001/* 002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 003 * 004 * Copyright (c) 2015-2017 Oracle and/or its affiliates. All rights reserved. 005 * 006 * The contents of this file are subject to the terms of either the GNU 007 * General Public License Version 2 only ("GPL") or the Common Development 008 * and Distribution License("CDDL") (collectively, the "License"). You 009 * may not use this file except in compliance with the License. You can 010 * obtain a copy of the License at 011 * https://oss.oracle.com/licenses/CDDL+GPL-1.1 012 * or LICENSE.txt. See the License for the specific 013 * language governing permissions and limitations under the License. 014 * 015 * When distributing the software, include this License Header Notice in each 016 * file and include the License file at LICENSE.txt. 017 * 018 * GPL Classpath Exception: 019 * Oracle designates this particular file as subject to the "Classpath" 020 * exception as provided by Oracle in the GPL Version 2 section of the License 021 * file that accompanied this code. 022 * 023 * Modifications: 024 * If applicable, add the following below the License Header, with the fields 025 * enclosed by brackets [] replaced by your own identifying information: 026 * "Portions Copyright [year] [name of copyright owner]" 027 * 028 * Contributor(s): 029 * If you wish your version of this file to be governed by only the CDDL or 030 * only the GPL Version 2, indicate your decision by adding "[Contributor] 031 * elects to include this software in this distribution under the [CDDL or GPL 032 * Version 2] license." If you don't indicate a single choice of license, a 033 * recipient has the option to distribute your version of this file under 034 * either the CDDL, the GPL Version 2 or to extend the choice of license to 035 * its licensees as provided above. However, if you add GPL Version 2 code 036 * and therefore, elected the GPL Version 2 license, then the option applies 037 * only if the new code is made subject to such option by the copyright 038 * holder. 039 */ 040 041package javax.json.stream; 042 043import java.util.Map; 044import java.util.HashMap; 045import java.util.stream.Collector; 046import java.util.function.BinaryOperator; 047import java.util.function.Function; 048import java.util.function.BiConsumer; 049 050import javax.json.Json; 051import javax.json.JsonArray; 052import javax.json.JsonArrayBuilder; 053import javax.json.JsonObject; 054import javax.json.JsonObjectBuilder; 055import javax.json.JsonValue; 056import javax.json.JsonException; 057 058/** 059 * This class contains some implementations of {@code java.util.stream.Collector} for accumulating 060 * {@link JsonValue}s into {@link JsonArray} and {@link JsonObject}. 061 * 062 * <EMBED CLASS='external-html' DATA-FILE-ID=LICENSE DATA-CIETName=JsonCollectors> 063 * 064 * @since 1.1 065 */ 066public final class JsonCollectors { 067 068 private JsonCollectors() { 069 } 070 071 /** 072 * Constructs a {@code java.util.stream.Collector} that accumulates the input {@code JsonValue} 073 * elements into a {@code JsonArray}. 074 * 075 * @return the constructed Collector 076 */ 077 public static Collector<JsonValue, JsonArrayBuilder, JsonArray> toJsonArray() { 078 return Collector.of( 079 Json::createArrayBuilder, 080 JsonArrayBuilder::add, 081 JsonArrayBuilder::addAll, 082 JsonArrayBuilder::build); 083 } 084 085 /** 086 * Constructs a {@code java.util.stream.Collector} that accumulates the input {@code Map.Entry<String,JsonValue>} 087 * elements into a {@code JsonObject}. 088 * 089 * @return the constructed Collector 090 */ 091 public static Collector<Map.Entry<String, JsonValue>, JsonObjectBuilder, JsonObject> toJsonObject() { 092 return Collector.of( 093 Json::createObjectBuilder, 094 (JsonObjectBuilder b, Map.Entry<String, JsonValue> v) -> b.add(v.getKey(), v.getValue()), 095 JsonObjectBuilder::addAll, 096 JsonObjectBuilder::build); 097 } 098 099 /** 100 * Constructs a {@code java.util.stream.Collector} that accumulates the input {@code JsonValue} 101 * elements into a {@code JsonObject}. The name/value pairs of the {@code JsonObject} are computed 102 * by applying the provided mapping functions. 103 * 104 * @param keyMapper a mapping function to produce names. 105 * @param valueMapper a mapping function to produce values 106 * @return the constructed Collector 107 */ 108 public static Collector<JsonValue, JsonObjectBuilder, JsonObject> 109 toJsonObject(Function<JsonValue, String> keyMapper, 110 Function<JsonValue, JsonValue> valueMapper) { 111 return Collector.of( 112 Json::createObjectBuilder, 113 (b, v) -> b.add(keyMapper.apply(v), valueMapper.apply(v)), 114 JsonObjectBuilder::addAll, 115 JsonObjectBuilder::build, 116 Collector.Characteristics.UNORDERED); 117 } 118 119 /** 120 * Constructs a {@code java.util.stream.Collector} that implements a "group by" operation on the 121 * input {@code JsonValue} elements. A classifier function maps the input {@code JsonValue}s to keys, and 122 * the {@code JsonValue}s are partitioned into groups according to the value of the key. 123 * A reduction operation is performed on the {@code JsonValue}s in each group, using the 124 * downstream {@code Collector}. For each group, the key and the results of the reduction operation 125 * become the name/value pairs of the resultant {@code JsonObject}. 126 * 127 * @param <T> the intermediate accumulation {@code JsonArrayBuilder} of the downstream collector 128 * @param classifier a function mapping the input {@code JsonValue}s to a String, producing keys 129 * @param downstream a {@code Collector} that implements a reduction operation on the 130 * {@code JsonValue}s in each group. 131 * @return the constructed {@code Collector} 132 */ 133 public static <T extends JsonArrayBuilder> Collector<JsonValue, Map<String, T>, JsonObject> 134 groupingBy(Function<JsonValue, String> classifier, 135 Collector<JsonValue, T, JsonArray> downstream) { 136 137 BiConsumer<Map<String, T>, JsonValue> accumulator = 138 (map, value) -> { 139 String key = classifier.apply(value); 140 if (key == null) { 141 throw new JsonException("element cannot be mapped to a null key"); 142 } 143 // Build a map of key to JsonArrayBuilder 144 T arrayBuilder = 145 map.computeIfAbsent(key, v->downstream.supplier().get()); 146 // Add elements from downstream Collector to the arrayBuilder. 147 downstream.accumulator().accept(arrayBuilder, value); 148 }; 149 Function<Map<String, T>, JsonObject> finisher = 150 map -> { 151 // transform the map of name: JsonArrayBuilder to 152 // name: JsonArray 153 // using the downstream collector for reducing the JsonArray 154 JsonObjectBuilder objectBuilder = Json.createObjectBuilder(); 155 map.forEach((k, v) -> { 156 JsonArray array = downstream.finisher().apply(v); 157 objectBuilder.add(k, array); 158 }); 159 return objectBuilder.build(); 160 }; 161 BinaryOperator<Map<String, T>> combiner = 162 (map1, map2) -> { 163 map1.putAll(map2); 164 return map1; 165 }; 166 return Collector.of(HashMap::new, accumulator, combiner, finisher, 167 Collector.Characteristics.UNORDERED); 168 } 169 170 /** 171 * Constructs a {@code java.util.stream.Collector} that implements a "group by" operation on the 172 * input {@code JsonValue} elements. A classifier function maps the input {@code JsonValue}s to keys, and 173 * the {@code JsonValue}s are partitioned into groups according to the value of the key. 174 * The {@code JsonValue}s in each group are added to a {@code JsonArray}. The key and the 175 * {@code JsonArray} in each group becomes the name/value pair of the resultant {@code JsonObject}. 176 * 177 * @param classifier a function mapping the input {@code JsonValue}s to a String, producing keys 178 * @return the constructed {@code Collector} 179 */ 180 public static Collector<JsonValue, Map<String, JsonArrayBuilder>, JsonObject> 181 groupingBy(Function<JsonValue, String> classifier) { 182 return groupingBy(classifier, toJsonArray()); 183 } 184} 185