Java tutorial
/* * Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com) * * This software is dual-licensed under: * * - the Lesser General Public License (LGPL) version 3.0 or, at your option, any * later version; * - the Apache Software License (ASL) version 2.0. * * The text of this file and of both licenses is available at the root of this * project or, if you have the jar distribution, in directory META-INF/, under * the names LGPL-3.0.txt and ASL-2.0.txt respectively. * * Direct link to the sources: * * - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt * - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt */ package com.github.fge.jsonschema.core.report; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.NullNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.fge.jackson.JacksonUtils; import com.github.fge.jsonschema.core.exceptions.ExceptionProvider; import com.github.fge.jsonschema.core.exceptions.ProcessingException; import com.github.fge.jsonschema.core.messages.JsonSchemaCoreMessageBundle; import com.github.fge.jsonschema.core.util.AsJson; import com.github.fge.msgsimple.bundle.MessageBundle; import com.github.fge.msgsimple.load.MessageBundles; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import javax.annotation.concurrent.NotThreadSafe; import java.util.Formatter; import java.util.IllegalFormatException; import java.util.List; import java.util.Map; /** * One processing message * * <p>Internally, a processing message has a {@link Map} whose keys are strings * and values are {@link JsonNode}s. Note that all methods altering the message * contents accept {@code null}: in this case, the value for that key will be a * {@link NullNode}. If you submit null <i>keys</i>, the whole value will be * <b>ignored</b>.</p> * * <p>Some methods to append contents to a message accept arbitrary inputs: in * this case, it is your responsibility to ensure that these inputs have a * correct implementation of {@link Object#toString()}.</p> * * <p>You can use formatted strings as messages using the capabilities of {@link * Formatter}; in order to pass arguments to the different placeholders, you * will then use the {@code .putArgument()} methods instead of {@code .put()}. * Arguments will appear in the order you submit them.</p> * * <p>Please note that if you do:</p> * * <pre> * message.setMessage("foo %s").putArgument("something", "here") * .setMessage("another %s message") * </pre> * * <p>then the argument list is <b>cleared</b>.</p> * * <p>You can alter the behaviour of a processing message in two ways: its log * level and its {@link ExceptionProvider} (used in {@link #asException()}.</p> * * <p>All mutation methods of a message return {@code this}.</p> */ @NotThreadSafe public final class ProcessingMessage implements AsJson { private static final MessageBundle BUNDLE = MessageBundles.getBundle(JsonSchemaCoreMessageBundle.class); private static final JsonNodeFactory FACTORY = JacksonUtils.nodeFactory(); /** * This is where all key/value pairs go */ private final Map<String, JsonNode> map = Maps.newLinkedHashMap(); /** * Argument list for Formatter */ private final List<Object> args = Lists.newArrayList(); /** * Exception provider */ private ExceptionProvider exceptionProvider = SimpleExceptionProvider.getInstance(); private LogLevel level; /** * Constructor * * <p>By default, a message is generated with a log level of {@link * LogLevel#INFO}.</p> */ public ProcessingMessage() { setLogLevel(LogLevel.INFO); } /** * Get the main message * * @return the main message as a string */ public String getMessage() { return map.containsKey("message") ? map.get("message").textValue() : "(no message)"; } /** * Get the log level for this message * * @return the log level */ public LogLevel getLogLevel() { return level; } /** * Set the main message * * @param message the message as a string * @return this */ public ProcessingMessage setMessage(final String message) { args.clear(); return put("message", message); } /** * Set the log level for this message * * @param level the log level * @return this * @throws NullPointerException log level is null */ public ProcessingMessage setLogLevel(final LogLevel level) { BUNDLE.checkNotNull(level, "processing.nullLevel"); this.level = level; return put("level", level); } /** * Set the exception provider for that particular message * * @param exceptionProvider the exception provider * @return this * @throws NullPointerException exception provider is null */ public ProcessingMessage setExceptionProvider(final ExceptionProvider exceptionProvider) { BUNDLE.checkNotNull(exceptionProvider, "processing.nullExceptionProvider"); this.exceptionProvider = exceptionProvider; return this; } /** * Add a key/value pair to this message * * <p>This is the main method. All other put methods call this one.</p> * * <p>Note that if the key is {@code null}, the content is <b>ignored</b>. * </p> * * @param key the key * @param value the value as a {@link JsonNode} * @return this */ public ProcessingMessage put(final String key, final JsonNode value) { if (key == null) return this; if (value == null) return putNull(key); map.put(key, value.deepCopy()); return this; } /** * Add a key/value pair to this message, which is also a formatter argument * * @param key the key * @param value the value * @return this */ public ProcessingMessage putArgument(final String key, final JsonNode value) { addArgument(key, value); return put(key, value); } /** * Add a key/value pair to this message * * @param key the key * @param asJson the value, which implements {@link AsJson} * @return this */ public ProcessingMessage put(final String key, final AsJson asJson) { return put(key, asJson.asJson()); } /** * Add a key/value pair to this message, which is also a formatter argument * * @param key the key * @param asJson the value * @return this */ public ProcessingMessage putArgument(final String key, final AsJson asJson) { addArgument(key, asJson.asJson()); return put(key, asJson); } /** * Add a key/value pair to this message * * @param key the key * @param value the value * @return this */ public ProcessingMessage put(final String key, final String value) { return value == null ? putNull(key) : put(key, FACTORY.textNode(value)); } /** * Add a key/value pair to this message * * @param key the key * @param value the value as an integer * @return this */ public ProcessingMessage put(final String key, final int value) { return put(key, FACTORY.numberNode(value)); } /** * Add a key/value pair to this message, which is also a formatter argument * * @param key the key * @param value the value * @return this */ public ProcessingMessage putArgument(final String key, final int value) { addArgument(key, value); return put(key, value); } /** * Add a key/value pair to this message * * <p>Ensure that {@code toString()} is correctly implemented.</p> * * @param key the key * @param value the value * @param <T> the type of the value * @return this */ public <T> ProcessingMessage put(final String key, final T value) { return value == null ? putNull(key) : put(key, FACTORY.textNode(value.toString())); } /** * Add a key/value pair to this message, which is also a formatter argument * * @param key the key * @param value the value * @param <T> the type of the value * @return this */ public <T> ProcessingMessage putArgument(final String key, final T value) { addArgument(key, value); return put(key, value); } /** * Add a key/value pair to this message, where the value is a collection of * items * * <p>This will put all values (again, using {@link #toString()} of the * collection into an array.</p> * * @param key the key * @param values the collection of values * @param <T> the element type of the collection * @return this */ public <T> ProcessingMessage put(final String key, final Iterable<T> values) { if (values == null) return putNull(key); final ArrayNode node = FACTORY.arrayNode(); for (final T value : values) node.add(value == null ? FACTORY.nullNode() : FACTORY.textNode(value.toString())); return put(key, node); } /** * Add a key/value pair to this message, which is also a formatter argument * * <p>Note that the collection will not be "exploded" into its individual * arguments.</p> * * @param key the key * @param <T> type of values * @param values the collection of values * @return this */ public <T> ProcessingMessage putArgument(final String key, final Iterable<T> values) { addArgument(key, values); return put(key, values); } private void addArgument(final String key, final Object value) { if (key != null) args.add(value); if (!map.containsKey("message")) return; final String fmt = map.get("message").textValue(); try { final String formatted = new Formatter().format(fmt, args.toArray()).toString(); map.put("message", FACTORY.textNode(formatted)); } catch (IllegalFormatException ignored) { } } /** * Put a {@link NullNode} as a value for a key * * <p>Note: if {@code key} is null, the put will be <b>ignored</b>.</p> * * @param key the key * @return this */ private ProcessingMessage putNull(final String key) { if (key == null) return this; map.put(key, FACTORY.nullNode()); return this; } @Override public JsonNode asJson() { final ObjectNode ret = FACTORY.objectNode(); ret.putAll(map); return ret; } /** * Build an exception out of this message * * <p>This uses the {@link ExceptionProvider} built into the message and * invokes {@link ExceptionProvider#doException(ProcessingMessage)} with * {@code this} as an argument.</p> * * @return an exception * @see #setExceptionProvider(ExceptionProvider) */ public ProcessingException asException() { return exceptionProvider.doException(this); } @Override public String toString() { final Map<String, JsonNode> tmp = Maps.newLinkedHashMap(map); final JsonNode node = tmp.remove("message"); final String message = node == null ? "(no message)" : node.textValue(); final StringBuilder sb = new StringBuilder().append(level).append(": "); sb.append(message); for (final Map.Entry<String, JsonNode> entry : tmp.entrySet()) sb.append("\n ").append(entry.getKey()).append(": ").append(entry.getValue()); return sb.append('\n').toString(); } }