001/*
002 * acme4j - Java ACME client
003 *
004 * Copyright (C) 2015 Richard "Shred" K├Ârber
005 *   http://acme4j.shredzone.org
006 *
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
013 */
014package org.shredzone.acme4j.toolbox;
015
016import static org.shredzone.acme4j.toolbox.AcmeUtils.base64UrlEncode;
017
018import java.security.Key;
019import java.security.PublicKey;
020import java.time.Duration;
021import java.time.Instant;
022import java.time.format.DateTimeFormatter;
023import java.util.Collection;
024import java.util.Collections;
025import java.util.LinkedHashMap;
026import java.util.Map;
027import java.util.Objects;
028
029import edu.umd.cs.findbugs.annotations.Nullable;
030import org.jose4j.json.JsonUtil;
031
032/**
033 * Builder for JSON structures.
034 * <p>
035 * Example:
036 * <pre>
037 * JSONBuilder cb = new JSONBuilder();
038 * cb.put("foo", 123).put("bar", "hello world");
039 * cb.object("sub").put("data", "subdata");
040 * cb.array("array", 123, 456, 789);
041 * </pre>
042 */
043public class JSONBuilder {
044
045    private final Map<String, Object> data = new LinkedHashMap<>();
046
047    /**
048     * Puts a property. If a property with the key exists, it will be replaced.
049     *
050     * @param key
051     *            Property key
052     * @param value
053     *            Property value
054     * @return {@code this}
055     */
056    public JSONBuilder put(String key, @Nullable Object value) {
057        data.put(Objects.requireNonNull(key, "key"), value);
058        return this;
059    }
060
061    /**
062     * Puts an {@link Instant} to the JSON. If a property with the key exists, it will be
063     * replaced.
064     *
065     * @param key
066     *            Property key
067     * @param value
068     *            Property {@link Instant} value
069     * @return {@code this}
070     */
071    public JSONBuilder put(String key, @Nullable Instant value) {
072        if (value == null) {
073            put(key, (Object) null);
074            return this;
075        }
076
077        put(key, DateTimeFormatter.ISO_INSTANT.format(value));
078        return this;
079    }
080
081    /**
082     * Puts a {@link Duration} to the JSON. If a property with the key exists, it will be
083     * replaced.
084     *
085     * @param key
086     *            Property key
087     * @param value
088     *            Property {@link Duration} value
089     * @return {@code this}
090     * @since 2.3
091     */
092    public JSONBuilder put(String key, @Nullable Duration value) {
093        if (value == null) {
094            put(key, (Object) null);
095            return this;
096        }
097
098        put(key, value.getSeconds());
099        return this;
100    }
101
102    /**
103     * Puts binary data to the JSON. The data is base64 url encoded.
104     *
105     * @param key
106     *            Property key
107     * @param data
108     *            Property data
109     * @return {@code this}
110     */
111    public JSONBuilder putBase64(String key, byte[] data) {
112        return put(key, base64UrlEncode(data));
113    }
114
115    /**
116     * Puts a {@link Key} into the claim. The key is serializied as JWK.
117     *
118     * @param key
119     *            Property key
120     * @param publickey
121     *            {@link PublicKey} to serialize
122     * @return {@code this}
123     */
124    public JSONBuilder putKey(String key, PublicKey publickey) {
125        Objects.requireNonNull(publickey, "publickey");
126
127        data.put(key, JoseUtils.publicKeyToJWK(publickey));
128        return this;
129    }
130
131    /**
132     * Creates an object for the given key.
133     *
134     * @param key
135     *            Key of the object
136     * @return Newly created {@link JSONBuilder} for the object.
137     */
138    public JSONBuilder object(String key) {
139        var subBuilder = new JSONBuilder();
140        data.put(key, subBuilder.data);
141        return subBuilder;
142    }
143
144    /**
145     * Puts an array.
146     *
147     * @param key
148     *            Property key
149     * @param values
150     *            Collection of property values
151     * @return {@code this}
152     */
153    public JSONBuilder array(String key, Collection<?> values) {
154        data.put(key, values);
155        return this;
156    }
157
158    /**
159     * Returns a {@link Map} representation of the current state.
160     *
161     * @return {@link Map} of the current state
162     */
163    public Map<String, Object> toMap() {
164        return Collections.unmodifiableMap(data);
165    }
166
167    /**
168     * Returns a {@link JSON} representation of the current state.
169     *
170     * @return {@link JSON} of the current state
171     */
172    public JSON toJSON() {
173        return JSON.parse(toString());
174    }
175
176    /**
177     * Returns a JSON string representation of the current state.
178     */
179    @Override
180    public String toString() {
181        return JsonUtil.toJson(data);
182    }
183
184}