blob: d68ed14d21530ae5e66d5eae59c5e0483a646f48 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.jclouds.ohai.functions;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.jclouds.domain.JsonBall;
import org.jclouds.json.Json;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.inject.TypeLiteral;
@Singleton
public class NestSlashKeys implements Function<Multimap<String, Supplier<JsonBall>>, Map<String, JsonBall>> {
private final Json json;
@Inject
NestSlashKeys(Json json) {
this.json = checkNotNull(json, "json");
}
@Override
public Map<String, JsonBall> apply(Multimap<String, Supplier<JsonBall>> from) {
Map<String, JsonBall> autoAttrs = mergeSameKeys(from);
Map<String, JsonBall> modifiableFlatMap = Maps.newLinkedHashMap(Maps.filterKeys(autoAttrs,
new Predicate<String>() {
@Override
public boolean apply(String input) {
return input.indexOf('/') == -1;
}
}));
Map<String, JsonBall> withSlashesMap = Maps.difference(autoAttrs, modifiableFlatMap).entriesOnlyOnLeft();
for (Entry<String, JsonBall> entry : withSlashesMap.entrySet()) {
List<String> keyParts = Lists.newArrayList(Splitter.on('/').split(entry.getKey()));
JsonBall toInsert = entry.getValue();
try {
putUnderContext(keyParts, toInsert, modifiableFlatMap);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("error inserting value in entry: " + entry.getKey(), e);
}
}
return modifiableFlatMap;
}
private Map<String, JsonBall> mergeSameKeys(Multimap<String, Supplier<JsonBall>> from) {
Map<String, JsonBall> merged = Maps.newLinkedHashMap();
for (Entry<String, Supplier<JsonBall>> entry : from.entries()) {
if (merged.containsKey(entry.getKey())) {
mergeAsPeer(entry.getKey(), entry.getValue().get(), merged);
} else {
merged.put(entry.getKey(), entry.getValue().get());
}
}
return merged;
}
@VisibleForTesting
void mergeAsPeer(String key, JsonBall value, Map<String, JsonBall> insertionContext) {
Map<String, JsonBall> immutableValueContext = json.fromJson(insertionContext.get(key).toString(), mapLiteral);
Map<String, JsonBall> valueContext = Maps.newLinkedHashMap(immutableValueContext);
Map<String, JsonBall> toPut = json.<Map<String, JsonBall>> fromJson(value.toString(), mapLiteral);
Set<String> uniques = Sets.difference(toPut.keySet(), valueContext.keySet());
for (String k : uniques) {
valueContext.put(k, toPut.get(k));
}
Set<String> conflicts = Sets.difference(toPut.keySet(), uniques);
for (String k : conflicts) {
JsonBall v = toPut.get(k);
if (v.toString().matches("^\\{.*\\}$")) {
mergeAsPeer(k, v, valueContext);
} else {
// replace
valueContext.put(k, v);
}
}
insertionContext.put(key, new JsonBall(json.toJson(valueContext, mapLiteral)));
}
/**
* @param keyParts
* @param toInsert
* @param destination
* @throws IllegalArgumentException
* <p/>
* if destination.get(keyParts(0)) is not a map *
* <p/>
* keyParts is zero length
*/
void putUnderContext(List<String> keyParts, JsonBall toInsert, Map<String, JsonBall> destination) {
checkNotNull(keyParts, "keyParts");
checkArgument(keyParts.size() >= 1, "keyParts must contain at least one element");
checkNotNull(toInsert, "toInsert");
checkNotNull(destination, "destination");
String rootKey = keyParts.remove(0);
String rootValue = destination.containsKey(rootKey) ? destination.get(rootKey).toString() : "{}";
checkArgument(rootValue.matches("^\\{.*\\}$"), "value must be a hash: %s", rootValue);
Map<String, JsonBall> immutableInsertionContext = json.fromJson(rootValue, mapLiteral);
Map<String, JsonBall> insertionContext = Maps.newLinkedHashMap(immutableInsertionContext);
if (keyParts.size() == 1) {
if (!insertionContext.containsKey(keyParts.get(0))) {
insertionContext.put(keyParts.get(0), toInsert);
} else {
String key = keyParts.get(0);
mergeAsPeer(key, toInsert, insertionContext);
}
} else {
putUnderContext(keyParts, toInsert, insertionContext);
}
destination.put(rootKey, new JsonBall(json.toJson(insertionContext, mapLiteral)));
}
final Type mapLiteral = new TypeLiteral<Map<String, JsonBall>>() {
}.getType();
final Type listLiteral = new TypeLiteral<List<JsonBall>>() {
}.getType();
}