org.zeromq.ZConfig Maven / Gradle / Ivy
Show all versions of jeromq Show documentation
package org.zeromq;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Lets applications load, work with, and save configuration files.
* This is a minimal implementation of the ZeroMQ Property Language,
* which is a simple structured text format for configuration files.
*
* Here is an example ZPL stream and corresponding config structure:
*
*
* {@code
context
iothreads = 1
verbose = 1 # Ask for a trace
main
type = zqueue # ZMQ_DEVICE type
frontend
option
hwm = 1000
swap = 25000000 # 25MB
bind = 'inproc://addr1'
bind = 'ipc://addr2'
backend
bind = inproc://addr3
}
{@code
root Down = child
| Across = next
v
context-->main
| |
| v
| type=queue-->frontend-->backend
| | |
| | v
| | bind=inproc://addr3
| v
| option-->bind=inproc://addr1-->bind=ipc://addr2
| |
| v
| hwm=1000-->swap=25000000
v
iothreads=1-->verbose=false
}
*
* It can put and get values and save and load them to disk:
*
*
* {@code
* ZConfig conf = new ZConfig("root", null);
* conf.put("/curve/public-key","abcdef");
* String val = conf.get("/curve/public-key","fallback-defaultkey");
* conf.save("test.cert");
* ZConfig loaded = ZConfig.load("test.cert");
}
*/
public class ZConfig
{
private interface IVisitor
{
void handleNode(ZConfig node, int level) throws IOException;
}
private static final String LEFT = "^( *)([0-9a-zA-Z\\$\\-_@\\.&\\+\\/]+)";
private static final Pattern PTRN_CONTAINER = Pattern.compile(LEFT + "( *#.*)?$");
private static final Pattern PTRN_KEYVALUE = Pattern.compile(LEFT + " = ((\"|')(.*)(\\4)|(.*?))(#.*)?$");
private final String name;
private final Map children = new HashMap<>();
private final List comments = new LinkedList<>();
private String value;
public ZConfig(String name, ZConfig parent)
{
this.name = name;
if (parent != null) {
parent.children.put(name, this);
}
}
public ZConfig getChild(String name)
{
return children.get(name);
}
public Map getValues()
{
Map values = new HashMap<>();
fillValues("", values);
return values;
}
private void fillValues(String prefix, Map values)
{
for (Entry entry : children.entrySet()) {
String key = entry.getKey();
ZConfig child = entry.getValue();
assert (child != null);
if (child.value != null) {
values.put(prefix + key, child.value);
}
child.fillValues(prefix + key + '/', values);
}
}
public String getName()
{
return this.name;
}
public String getValue(String path)
{
return getValue(path, null);
}
public String getValue(String path, String defaultValue)
{
String[] pathElements = path.split("/");
ZConfig current = this;
for (String pathElem : pathElements) {
if (pathElem.isEmpty()) {
continue;
}
current = current.children.get(pathElem);
if (current == null) {
return defaultValue;
}
}
return current.value;
}
/**
* check if a value-path exists
* @param path
* @return true if value-path exists
*/
public boolean pathExists(String path)
{
String[] pathElements = path.split("/");
ZConfig current = this;
for (String pathElem : pathElements) {
if (pathElem.isEmpty()) {
continue;
}
current = current.children.get(pathElem);
if (current == null) {
return false;
}
}
return true;
}
/**
* add comment
* @param comment
*/
public void addComment(String comment)
{
comments.add(comment);
}
/**
* @param path
* @param value set value of config item
*/
public ZConfig putValue(String path, String value)
{
String[] pathElements = path.split("/");
ZConfig current = this;
for (String pathElement : pathElements) {
if (pathElement.isEmpty()) {
// ignore leading slashes
continue;
}
ZConfig container = current.children.get(pathElement);
if (container == null) {
container = new ZConfig(pathElement, current);
}
current = container;
}
current.value = value;
return current;
}
public void putValues(ZConfig src)
{
for (Entry entry : src.getValues().entrySet()) {
putValue(entry .getKey(), entry.getValue());
}
}
private void visit(ZConfig startNode, IVisitor handler, int level) throws IOException
{
handler.handleNode(startNode, level);
for (ZConfig node : startNode.children.values()) {
visit(node, handler, level + 1);
}
}
/**
* Saves the configuration to a file.
*
* This method will overwrite contents of existing file
* @param filename the path of the file to save the configuration into, or "-" to dump it to standard output
* @return the saved file or null if dumped to the standard output
* @throws IOException if unable to save the file.
*/
public File save(String filename) throws IOException
{
if ("-".equals(filename)) {
// print to console
try (Writer writer = new PrintWriter(System.out)) {
save(writer);
}
return null;
}
else { // write to file
final File file = new File(filename);
if (file.exists()) {
file.delete();
}
else {
// create necessary directories;
file.getParentFile().mkdirs();
}
try (Writer writer = new FileWriter(file)) {
save(writer);
}
return file;
}
}
public void save(final Writer writer) throws IOException
{
visit(this, (node, level) -> {
// First print comments
if (!node.comments.isEmpty()) {
for (String comment : node.comments) {
writer.append("# ").append(comment).append('\n');
}
writer.append("\n");
}
// now the values
if (level > 0) {
String prefix = level > 1 ? String.format("%" + ((level - 1) * 4) + "s", " ") : "";
writer.append(prefix);
if (node.value == null) {
writer.append(node.name).append("\n");
}
else {
writer.append(String.format("%s = \"%s\"\n", node.name, node.value));
}
}
}, 0);
}
public static ZConfig load(String filename) throws IOException
{
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
List content = new ArrayList<>();
String line = reader.readLine();
while (line != null) {
boolean irrelevant = line.matches("^ *#.*|^ *[0-9]+.*") // ignore comments
|| line.trim().isEmpty(); // ignore empty lines;
if (!irrelevant) {
content.add(line);
}
line = reader.readLine();
}
return load(new ZConfig("root", null), content, 0, new AtomicInteger());
}
}
private static ZConfig load(ZConfig parent, List content, int currentLevel, AtomicInteger lineNumber)
{
while (lineNumber.get() < content.size()) {
String currentLine = content.get(lineNumber.get());
Matcher container = PTRN_CONTAINER.matcher(currentLine);
if (container.find()) {
ZConfig child = child(parent, container, currentLevel, currentLine, lineNumber);
if (child == null) {
// jump back;
break;
}
load(child, content, currentLevel + 1, lineNumber);
}
else {
Matcher keyvalue = PTRN_KEYVALUE.matcher(currentLine);
if (keyvalue.find()) {
ZConfig child = child(parent, keyvalue, currentLevel, currentLine, lineNumber);
if (child == null) {
// jump back;
break;
}
String value = keyvalue.group(5);
if (value == null) {
value = keyvalue.group(7);
}
if (value != null) {
value = value.trim();
}
child.value = value;
}
else {
throw new ReadException("Couldn't process line", currentLine, lineNumber);
}
}
}
return parent;
}
private static ZConfig child(ZConfig parent, Matcher matcher, int currentLevel, String currentLine,
AtomicInteger lineNumber)
{
int level = matcher.group(1).length() / 4;
if (level > currentLevel) {
throw new ReadException("Level mismatch in line", currentLine, lineNumber);
}
else if (level < currentLevel) {
// jump back;
return null;
}
lineNumber.incrementAndGet();
return new ZConfig(matcher.group(2), parent);
}
public static class ReadException extends RuntimeException
{
private static final long serialVersionUID = 1L;
public final int currentLineNumber;
public final String currentLine;
public ReadException(String message, String currentLine, AtomicInteger currentLineNumber)
{
super(String.format("%s %s: %s", message, currentLineNumber, currentLine));
this.currentLine = currentLine;
this.currentLineNumber = currentLineNumber.get();
}
}
}