/** * Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org> * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.seedstack.seed.core.internal.configuration.tool; import org.seedstack.coffig.Config; import org.seedstack.coffig.SingleValue; import org.seedstack.coffig.util.Utils; import org.seedstack.shed.reflect.Annotations; import javax.validation.constraints.NotNull; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.MissingResourceException; import java.util.ResourceBundle; import java.util.SortedMap; import java.util.TreeMap; class Node implements Comparable<Node> { private final String name; private final Class<?> configClass; private final Class<?> outermostClass; private final int outermostLevel; private final String[] path; private final Map<String, PropertyInfo> propertyInfo; private final SortedMap<String, Node> children = new TreeMap<>(); Node() { this.name = ""; this.configClass = null; this.outermostClass = null; this.outermostLevel = 0; this.path = new String[0]; this.propertyInfo = new HashMap<>(); } Node(Class<?> configClass) { this.configClass = configClass; List<String> path = new ArrayList<>(); Class<?> previousClass = configClass; int nestingLevel = -1; do { Config annotation = configClass.getAnnotation(Config.class); if (annotation == null) { break; } List<String> splitPath = Arrays.asList(annotation.value().split("\\.")); Collections.reverse(splitPath); path.addAll(splitPath); previousClass = configClass; nestingLevel++; } while ((configClass = configClass.getDeclaringClass()) != null); Collections.reverse(path); this.outermostClass = previousClass; this.outermostLevel = this.outermostClass.getAnnotation(Config.class).value().split("\\.").length; this.path = path.toArray(new String[path.size()]); this.name = this.path[this.path.length - 1]; this.propertyInfo = buildPropertyInfo(); } String getName() { return name; } String[] getPath() { return path; } Node getChild(String name) { return children.get(name); } void addChild(Node node) { children.put(node.getName(), node); } Collection<Node> getChildren() { return Collections.unmodifiableCollection(children.values()); } Collection<PropertyInfo> getPropertyInfo() { return propertyInfo.values(); } PropertyInfo getPropertyInfo(String name) { return propertyInfo.get(name); } boolean isRootNode() { return configClass == null; } public String toString() { return String.join(".", (CharSequence[]) path); } Node find(String... path) { if (path.length == 0) { return this; } for (Node node : children.values()) { if (path[0].equals(node.name)) { return node.find(Arrays.copyOfRange(path, 1, path.length)); } } return null; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Node node = (Node) o; return o.toString().equals(node.toString()); } @Override public int hashCode() { return toString().hashCode(); } @Override public int compareTo(Node that) { if (this.toString().compareTo(that.toString()) < 0) { return -1; } else if (this.toString().compareTo(that.toString()) > 0) { return 1; } return 0; } private Map<String, PropertyInfo> buildPropertyInfo() { Map<String, PropertyInfo> result = new HashMap<>(); ResourceBundle bundle = null; try { bundle = ResourceBundle.getBundle(outermostClass.getName()); } catch (MissingResourceException e) { // ignore } Object defaultInstance = null; try { defaultInstance = Utils.instantiateDefault(configClass); } catch (Exception e) { // ignore } for (Field field : configClass.getDeclaredFields()) { if (Modifier.isStatic(field.getModifiers())) { continue; } if (field.getType().isAnnotationPresent(Config.class)) { continue; } PropertyInfo propertyInfo = new PropertyInfo(); Config configAnnotation = field.getAnnotation(Config.class); String name; if (configAnnotation != null) { name = configAnnotation.value(); } else { name = field.getName(); } field.setAccessible(true); propertyInfo.setName(name); propertyInfo.setShortDescription(getMessage(bundle, "No description.", buildKey(name))); propertyInfo.setLongDescription(getMessage(bundle, null, buildKey(name, "long"))); propertyInfo.setType(Utils.getSimpleTypeName(field.getGenericType())); propertyInfo.setSingleValue(field.isAnnotationPresent(SingleValue.class)); if (defaultInstance != null) { try { propertyInfo.setDefaultValue(field.get(defaultInstance)); } catch (IllegalAccessException e) { // ignore } } propertyInfo.setMandatory(propertyInfo.getDefaultValue() == null && Annotations.on(field).includingMetaAnnotations().find(NotNull.class).isPresent()); result.put(name, propertyInfo); } return result; } private String getMessage(ResourceBundle resourceBundle, String defaultMessage, String key) { if (resourceBundle == null) { return defaultMessage; } try { return resourceBundle.getString(key); } catch (MissingResourceException e) { return defaultMessage; } } private String buildKey(String... parts) { StringBuilder sb = new StringBuilder(); if (!configClass.equals(outermostClass)) { for (int i = outermostLevel; i < path.length; i++) { sb.append(path[i]); if (i < path.length - 1 || parts.length > 0) { sb.append("."); } } } for (int i = 0; i < parts.length; i++) { sb.append(parts[i]); if (i < parts.length - 1) { sb.append("."); } } return sb.toString(); } }