// Copyright 2017 JanusGraph Authors // // 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 org.janusgraph.diskstorage.configuration; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.HashCodeBuilder; import java.util.List; /** * @author Matthias Broecheler (me@matthiasb.com) */ public abstract class ConfigElement { public static final char SEPARATOR = '.'; public static final char[] ILLEGAL_CHARS = new char[]{SEPARATOR,' ','\t','#','@','<','>','?','/',';','"','\'',':','+','(',')','*','^','`','~','$','%','|','\\','{','[',']','}'}; private final ConfigNamespace namespace; private final String name; private final String description; public ConfigElement(ConfigNamespace namespace, String name, String description) { Preconditions.checkArgument(StringUtils.isNotBlank(name),"Name cannot be empty: %s",name); Preconditions.checkArgument(!StringUtils.containsAny(name, ILLEGAL_CHARS),"Name contains illegal character: %s (%s)",name,ILLEGAL_CHARS); Preconditions.checkArgument(namespace!=null || this instanceof ConfigNamespace,"Need to specify namespace for ConfigOption"); Preconditions.checkArgument(StringUtils.isNotBlank(description)); this.namespace = namespace; this.name = name; this.description = description; if (namespace!=null) namespace.registerChild(this); } public ConfigNamespace getNamespace() { Preconditions.checkArgument(namespace !=null,"Cannot get namespace of root"); return namespace; } public boolean isRoot() { return namespace ==null; } public ConfigNamespace getRoot() { if (isRoot()) return (ConfigNamespace)this; else return getNamespace().getRoot(); } public String getName() { return name; } public String getDescription() { return description; } public abstract boolean isOption(); public boolean isNamespace() { return !isOption(); } @Override public String toString() { return (namespace !=null? namespace.toString()+SEPARATOR:"") + name; } @Override public int hashCode() { return new HashCodeBuilder().append(name).append(namespace).toHashCode(); } @Override public boolean equals(Object oth) { if (this==oth) return true; else if (oth==null || !getClass().isInstance(oth)) return false; ConfigElement c = (ConfigElement)oth; return name.equals(c.name) && namespace ==c.namespace; } public static String[] getComponents(final String path) { return StringUtils.split(path,SEPARATOR); } public static String toStringSingle(ConfigElement element) { return toStringSingle(element,""); } private static String toStringSingle(ConfigElement element, String indent) { String result = element.getName(); if (element.isNamespace()) { result = "+ " + result; if (((ConfigNamespace)element).isUmbrella()) result += " [*]"; } else { result = "- " + result; ConfigOption option = (ConfigOption)element; result+= " ["; switch (option.getType()) { case FIXED: result+="f"; break; case GLOBAL_OFFLINE: result+="g!"; break; case GLOBAL: result+="g"; break; case MASKABLE: result+="m"; break; case LOCAL: result+="l"; break; } result+=","+option.getDatatype().getSimpleName(); result+=","+option.getDefaultValue(); result+="]"; } result = indent + result + "\n" + indent; String desc = element.getDescription(); result+="\t"+'"'+desc.substring(0, Math.min(desc.length(), 50))+'"'; return result; } public static String toString(ConfigElement element) { //return toStringRecursive(element,""); return toStringSingle(element, ""); } // private static String toStringRecursive(ConfigElement element, String indent) { // String result = toStringSingle(element, indent) + "\n"; // if (element.isNamespace()) { // ConfigNamespace ns = (ConfigNamespace)element; // indent += "\t"; // for (ConfigElement child : ns.getChildren()) { // result += toStringRecursive(child,indent); // } // } // return result; // } public static String getPath(ConfigElement element, String... umbrellaElements) { return getPath(element, false, umbrellaElements); } public static String getPath(ConfigElement element, boolean includeRoot, String... umbrellaElements) { Preconditions.checkNotNull(element); if (umbrellaElements==null) umbrellaElements = new String[0]; String path = element.getName(); int umbrellaPos = umbrellaElements.length-1; while (!element.isRoot() && !element.getNamespace().isRoot()) { ConfigNamespace parent = element.getNamespace(); if (parent.isUmbrella()) { Preconditions.checkArgument(umbrellaPos>=0,"Missing umbrella element path for element: %s",element); String umbrellaName = umbrellaElements[umbrellaPos]; Preconditions.checkArgument(!StringUtils.containsAny(umbrellaName,ILLEGAL_CHARS),"Invalid umbrella name provided: %s. Contains illegal chars",umbrellaName); path = umbrellaName + SEPARATOR + path; umbrellaPos--; } path = parent.getName() + SEPARATOR + path; element = parent; } if (includeRoot) { // Assumes that roots are not umbrellas // If roots could be umbrellas, we might have to change the interpretation of umbrellaElements path = (element.isRoot() ? element.getName() : element.getNamespace().getName()) + SEPARATOR + path; } //Don't make this check so that we can still access more general config items Preconditions.checkArgument(umbrellaPos<0,"Found unused umbrella element: %s",umbrellaPos<0?null:umbrellaElements[umbrellaPos]); return path; } public static PathIdentifier parse(ConfigNamespace root, String path) { Preconditions.checkNotNull(root); if (StringUtils.isBlank(path)) return new PathIdentifier(root,new String[]{},false); String[] components = getComponents(path); Preconditions.checkArgument(components.length>0,"Empty path provided: %s",path); List<String> umbrellaElements = Lists.newArrayList(); ConfigNamespace parent = root; ConfigElement last = root; boolean lastIsUmbrella = false; for (int i=0;i<components.length;i++) { if (parent.isUmbrella() && !lastIsUmbrella) { umbrellaElements.add(components[i]); lastIsUmbrella = true; } else { last = parent.getChild(components[i]); Preconditions.checkArgument(last!=null,"Unknown configuration element in namespace [%s]: %s",parent.toString(),components[i]); if (i+1<components.length) { Preconditions.checkArgument(last instanceof ConfigNamespace,"Expected namespace at position [%s] of [%s] but got: %s",i,path,last); parent = (ConfigNamespace)last; } lastIsUmbrella = false; } } return new PathIdentifier(last,umbrellaElements.toArray(new String[umbrellaElements.size()]), lastIsUmbrella); } public static class PathIdentifier { public final ConfigElement element; public final String[] umbrellaElements; public final boolean lastIsUmbrella; private PathIdentifier(ConfigElement element, String[] umbrellaElements, boolean lastIsUmbrella) { this.lastIsUmbrella = lastIsUmbrella; Preconditions.checkNotNull(element); Preconditions.checkNotNull(umbrellaElements); this.element = element; this.umbrellaElements = umbrellaElements; } public boolean hasUmbrellaElements() { return umbrellaElements.length>0; } } }