001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.util;
018
019import java.net.URI;
020import java.net.URL;
021import java.util.ArrayList;
022import java.util.LinkedHashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.regex.Matcher;
026import java.util.regex.Pattern;
027
028/**
029 * A helper class for <a href="http://json-schema.org/">JSON schema</a>.
030 */
031public final class JsonSchemaHelper {
032
033    private static final Pattern PATTERN = Pattern.compile("\"(.+?)\"|\\[(.+)\\]");
034    private static final String QUOT = "&quot;";
035
036    private JsonSchemaHelper() {
037    }
038
039    /**
040     * Gets the JSon schema type.
041     *
042     * @param type the java type
043     * @return the json schema type, is never null, but returns <tt>object</tt> as the generic type
044     */
045    public static String getType(Class<?> type) {
046        if (type.isEnum()) {
047            return "enum";
048        } else if (type.isArray()) {
049            return "array";
050        }
051        if (type.isAssignableFrom(URI.class) || type.isAssignableFrom(URL.class)) {
052            return "sting";
053        }
054
055        String primitive = getPrimitiveType(type);
056        if (primitive != null) {
057            return primitive;
058        }
059
060        return "object";
061    }
062
063    /**
064     * Gets the JSon schema primitive type.
065     *
066     * @param type the java type
067     * @return the json schema primitive type, or <tt>null</tt> if not a primitive
068     */
069    public static String getPrimitiveType(Class<?> type) {
070        String name = type.getCanonicalName();
071
072        // special for byte[] or Object[] as its common to use
073        if ("java.lang.byte[]".equals(name) || "byte[]".equals(name)) {
074            return "string";
075        } else if ("java.lang.Byte[]".equals(name) || "Byte[]".equals(name)) {
076            return "array";
077        } else if ("java.lang.Object[]".equals(name) || "Object[]".equals(name)) {
078            return "array";
079        } else if ("java.lang.String[]".equals(name) || "String[]".equals(name)) {
080            return "array";
081            // and these is common as well
082        } else if ("java.lang.String".equals(name) || "String".equals(name)) {
083            return "string";
084        } else if ("java.lang.Boolean".equals(name) || "Boolean".equals(name)) {
085            return "boolean";
086        } else if ("boolean".equals(name)) {
087            return "boolean";
088        } else if ("java.lang.Integer".equals(name) || "Integer".equals(name)) {
089            return "integer";
090        } else if ("int".equals(name)) {
091            return "integer";
092        } else if ("java.lang.Long".equals(name) || "Long".equals(name)) {
093            return "integer";
094        } else if ("long".equals(name)) {
095            return "integer";
096        } else if ("java.lang.Short".equals(name) || "Short".equals(name)) {
097            return "integer";
098        } else if ("short".equals(name)) {
099            return "integer";
100        } else if ("java.lang.Byte".equals(name) || "Byte".equals(name)) {
101            return "integer";
102        } else if ("byte".equals(name)) {
103            return "integer";
104        } else if ("java.lang.Float".equals(name) || "Float".equals(name)) {
105            return "number";
106        } else if ("float".equals(name)) {
107            return "number";
108        } else if ("java.lang.Double".equals(name) || "Double".equals(name)) {
109            return "number";
110        } else if ("double".equals(name)) {
111            return "number";
112        }
113
114        return null;
115    }
116
117    /**
118     * Parses the json schema to split it into a list or rows, where each row contains key value pairs with the metadata
119     *
120     * @param group the group to parse from such as <tt>component</tt>, <tt>componentProperties</tt>, or <tt>properties</tt>.
121     * @param json  the json
122     * @return a list of all the rows, where each row is a set of key value pairs with metadata
123     */
124    public static List<Map<String, String>> parseJsonSchema(String group, String json, boolean parseProperties) {
125        List<Map<String, String>> answer = new ArrayList<Map<String, String>>();
126        if (json == null) {
127            return answer;
128        }
129
130        boolean found = false;
131
132        // parse line by line
133        String[] lines = json.split("\n");
134        for (String line : lines) {
135            // we need to find the group first
136            if (!found) {
137                String s = line.trim();
138                found = s.startsWith("\"" + group + "\":") && s.endsWith("{");
139                continue;
140            }
141
142            // we should stop when we end the group
143            if (line.equals("  },") || line.equals("  }")) {
144                break;
145            }
146
147            // need to safe encode \" so we can parse the line
148            line = line.replaceAll("\"\\\\\"\"", '"' + QUOT + '"');
149
150            Map<String, String> row = new LinkedHashMap<String, String>();
151            Matcher matcher = PATTERN.matcher(line);
152
153            String key;
154            if (parseProperties) {
155                // when parsing properties the first key is given as name, so the first parsed token is the value of the name
156                key = "name";
157            } else {
158                key = null;
159            }
160            while (matcher.find()) {
161                if (key == null) {
162                    key = matcher.group(1);
163                } else {
164                    String value = matcher.group(1);
165                    if (value == null) {
166                        value = matcher.group(2);
167                        // its an enum so strip out " and trim spaces after comma
168                        value = value.replaceAll("\"", "");
169                        value = value.replaceAll(", ", ",");
170                    }
171                    if (value != null) {
172                        value = value.trim();
173                        // decode
174                        value = value.replaceAll(QUOT, "\"");
175                        value = decodeJson(value);
176                    }
177                    row.put(key, value);
178                    // reset
179                    key = null;
180                }
181            }
182            if (!row.isEmpty()) {
183                answer.add(row);
184            }
185        }
186
187        return answer;
188    }
189
190    private static String decodeJson(String value) {
191        // json encodes a \ as \\ so we need to decode from \\ back to \
192        if ("\\\\".equals(value)) {
193            value = "\\";
194        }
195        return value;
196    }
197
198    /**
199     * Is the property required
200     *
201     * @param rows the rows of properties
202     * @param name name of the property
203     * @return <tt>true</tt> if required, or <tt>false</tt> if not
204     */
205    public static boolean isPropertyRequired(List<Map<String, String>> rows, String name) {
206        for (Map<String, String> row : rows) {
207            boolean required = false;
208            boolean found = false;
209            if (row.containsKey("name")) {
210                found = name.equals(row.get("name"));
211            }
212            if (row.containsKey("required")) {
213                required = "true".equals(row.get("required"));
214            }
215            if (found) {
216                return required;
217            }
218        }
219        return false;
220    }
221
222    /**
223     * Gets the default value of the property
224     *
225     * @param rows the rows of properties
226     * @param name name of the property
227     * @return the default value or <tt>null</tt> if no default value exists
228     */
229    public static String getPropertyDefaultValue(List<Map<String, String>> rows, String name) {
230        for (Map<String, String> row : rows) {
231            String defaultValue = null;
232            boolean found = false;
233            if (row.containsKey("name")) {
234                found = name.equals(row.get("name"));
235            }
236            if (row.containsKey("defaultValue")) {
237                defaultValue = row.get("defaultValue");
238            }
239            if (found) {
240                return defaultValue;
241            }
242        }
243        return null;
244    }
245
246    /**
247     * Is the property multi valued
248     *
249     * @param rows the rows of properties
250     * @param name name of the property
251     * @return <tt>true</tt> if multi valued, or <tt>false</tt> if not
252     */
253    public static boolean isPropertyMultiValue(List<Map<String, String>> rows, String name) {
254        for (Map<String, String> row : rows) {
255            boolean multiValue = false;
256            boolean found = false;
257            if (row.containsKey("name")) {
258                found = name.equals(row.get("name"));
259            }
260            if (row.containsKey("multiValue")) {
261                multiValue = "true".equals(row.get("multiValue"));
262            }
263            if (found) {
264                return multiValue;
265            }
266        }
267        return false;
268    }
269
270    /**
271     * Gets the prefix value of the property
272     *
273     * @param rows the rows of properties
274     * @param name name of the property
275     * @return the prefix value or <tt>null</tt> if no prefix value exists
276     */
277    public static String getPropertyPrefix(List<Map<String, String>> rows, String name) {
278        for (Map<String, String> row : rows) {
279            String prefix = null;
280            boolean found = false;
281            if (row.containsKey("name")) {
282                found = name.equals(row.get("name"));
283            }
284            if (row.containsKey("prefix")) {
285                prefix = row.get("prefix");
286            }
287            if (found) {
288                return prefix;
289            }
290        }
291        return null;
292    }
293
294}