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    // 0 = text, 1 = enum, 2 = boolean, 3 = integer or number
034    private static final Pattern PATTERN = Pattern.compile("\"(.+?)\"|\\[(.+)\\]|(true|false)|(-?\\d+\\.?\\d*)");
035    private static final String QUOT = "&quot;";
036
037    private JsonSchemaHelper() {
038    }
039
040    /**
041     * Gets the JSon schema type.
042     *
043     * @param type the java type
044     * @return the json schema type, is never null, but returns <tt>object</tt> as the generic type
045     */
046    public static String getType(Class<?> type) {
047        if (type.isEnum()) {
048            return "enum";
049        } else if (type.isArray()) {
050            return "array";
051        }
052        if (type.isAssignableFrom(URI.class) || type.isAssignableFrom(URL.class)) {
053            return "string";
054        }
055
056        String primitive = getPrimitiveType(type.getCanonicalName());
057        if (primitive != null) {
058            return primitive;
059        }
060
061        return "object";
062    }
063
064    /**
065     * Gets the JSon schema primitive type.
066     *
067     * @param   name the java type
068     * @return  the json schema primitive type, or <tt>null</tt> if not a primitive
069     */
070    public static String getPrimitiveType(String name) {
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        } else if ("java.lang.Character".equals(name) || "Character".equals(name) || "char".equals(name)) {
082            return "string";
083        } else if ("java.lang.String".equals(name) || "String".equals(name)) {
084            return "string";
085        } else if ("java.lang.Boolean".equals(name) || "Boolean".equals(name) || "boolean".equals(name)) {
086            return "boolean";
087        } else if ("java.lang.Integer".equals(name) || "Integer".equals(name) || "int".equals(name)) {
088            return "integer";
089        } else if ("java.lang.Long".equals(name) || "Long".equals(name) || "long".equals(name)) {
090            return "integer";
091        } else if ("java.lang.Short".equals(name) || "Short".equals(name) || "short".equals(name)) {
092            return "integer";
093        } else if ("java.lang.Byte".equals(name) || "Byte".equals(name) || "byte".equals(name)) {
094            return "integer";
095        } else if ("java.lang.Float".equals(name) || "Float".equals(name) || "float".equals(name)) {
096            return "number";
097        } else if ("java.lang.Double".equals(name) || "Double".equals(name) || "double".equals(name)) {
098            return "number";
099        }
100
101        return null;
102    }
103
104    /**
105     * Parses the json schema to split it into a list or rows, where each row contains key value pairs with the metadata
106     *
107     * @param group the group to parse from such as <tt>component</tt>, <tt>componentProperties</tt>, or <tt>properties</tt>.
108     * @param json the json
109     * @return a list of all the rows, where each row is a set of key value pairs with metadata
110     */
111    public static List<Map<String, String>> parseJsonSchema(String group, String json, boolean parseProperties) {
112        List<Map<String, String>> answer = new ArrayList<Map<String, String>>();
113        if (json == null) {
114            return answer;
115        }
116
117        boolean found = false;
118
119        // parse line by line
120        String[] lines = json.split("\n");
121        for (String line : lines) {
122            // we need to find the group first
123            if (!found) {
124                String s = line.trim();
125                found = s.startsWith("\"" + group + "\":") && s.endsWith("{");
126                continue;
127            }
128
129            // we should stop when we end the group
130            if (line.equals("  },") || line.equals("  }")) {
131                break;
132            }
133
134            // need to safe encode \" so we can parse the line
135            line = line.replaceAll("\"\\\\\"\"", '"' + QUOT + '"');
136
137            Map<String, String> row = new LinkedHashMap<String, String>();
138            Matcher matcher = PATTERN.matcher(line);
139
140            String key;
141            if (parseProperties) {
142                // when parsing properties the first key is given as name, so the first parsed token is the value of the name
143                key = "name";
144            } else {
145                key = null;
146            }
147            while (matcher.find()) {
148                if (key == null) {
149                    key = matcher.group(1);
150                } else {
151                    String value = matcher.group(1);
152                    if (value != null) {
153                        // its text based
154                        value = value.trim();
155                        // decode
156                        value = value.replaceAll(QUOT, "\"");
157                        value = decodeJson(value);
158                    }
159                    if (value == null) {
160                        // not text then its maybe an enum?
161                        value = matcher.group(2);
162                        if (value != null) {
163                            // its an enum so strip out " and trim spaces after comma
164                            value = value.replaceAll("\"", "");
165                            value = value.replaceAll(", ", ",");
166                            value = value.trim();
167                        }
168                    }
169                    if (value == null) {
170                        // not text then its maybe a boolean?
171                        value = matcher.group(3);
172                    }
173                    if (value == null) {
174                        // not text then its maybe a integer?
175                        value = matcher.group(4);
176                    }
177                    if (value != null) {
178                        row.put(key, value);
179                    }
180                    // reset
181                    key = null;
182                }
183            }
184            if (!row.isEmpty()) {
185                answer.add(row);
186            }
187        }
188
189        return answer;
190    }
191
192    private static String decodeJson(String value) {
193        // json encodes a \ as \\ so we need to decode from \\ back to \
194        if ("\\\\".equals(value)) {
195            value = "\\";
196        }
197        return value;
198    }
199
200    /**
201     * Is the property required
202     *
203     * @param rows the rows of properties
204     * @param name name of the property
205     * @return <tt>true</tt> if required, or <tt>false</tt> if not
206     */
207    public static boolean isPropertyRequired(List<Map<String, String>> rows, String name) {
208        for (Map<String, String> row : rows) {
209            boolean required = false;
210            boolean found = false;
211            if (row.containsKey("name")) {
212                found = name.equals(row.get("name"));
213            }
214            if (row.containsKey("required")) {
215                required = "true".equals(row.get("required"));
216            }
217            if (found) {
218                return required;
219            }
220        }
221        return false;
222    }
223
224    /**
225     * Gets the default value of the property
226     *
227     * @param rows the rows of properties
228     * @param name name of the property
229     * @return the default value or <tt>null</tt> if no default value exists
230     */
231    public static String getPropertyDefaultValue(List<Map<String, String>> rows, String name) {
232        for (Map<String, String> row : rows) {
233            String defaultValue = null;
234            boolean found = false;
235            if (row.containsKey("name")) {
236                found = name.equals(row.get("name"));
237            }
238            if (row.containsKey("defaultValue")) {
239                defaultValue = row.get("defaultValue");
240            }
241            if (found) {
242                return defaultValue;
243            }
244        }
245        return null;
246    }
247
248    /**
249     * Is the property multi valued
250     *
251     * @param rows the rows of properties
252     * @param name name of the property
253     * @return <tt>true</tt> if multi valued, or <tt>false</tt> if not
254     */
255    public static boolean isPropertyMultiValue(List<Map<String, String>> rows, String name) {
256        for (Map<String, String> row : rows) {
257            boolean multiValue = false;
258            boolean found = false;
259            if (row.containsKey("name")) {
260                found = name.equals(row.get("name"));
261            }
262            if (row.containsKey("multiValue")) {
263                multiValue = "true".equals(row.get("multiValue"));
264            }
265            if (found) {
266                return multiValue;
267            }
268        }
269        return false;
270    }
271
272    /**
273     * Gets the prefix value of the property
274     *
275     * @param rows the rows of properties
276     * @param name name of the property
277     * @return the prefix value or <tt>null</tt> if no prefix value exists
278     */
279    public static String getPropertyPrefix(List<Map<String, String>> rows, String name) {
280        for (Map<String, String> row : rows) {
281            String prefix = null;
282            boolean found = false;
283            if (row.containsKey("name")) {
284                found = name.equals(row.get("name"));
285            }
286            if (row.containsKey("prefix")) {
287                prefix = row.get("prefix");
288            }
289            if (found) {
290                return prefix;
291            }
292        }
293        return null;
294    }
295
296}