mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 03:57:51 +02:00
This change renames directories in preparation for the great package rename. The repository is now in a broken state because the code itself hasn't been updated. However this should ensure that git correctly preserves history for each file.
173 lines
6.4 KiB
Java
173 lines
6.4 KiB
Java
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
|
//
|
|
// Licensed 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 com.google.domain.registry.util;
|
|
|
|
import static com.google.common.base.Predicates.notNull;
|
|
import static com.google.common.collect.Lists.newArrayList;
|
|
|
|
import com.google.common.annotations.VisibleForTesting;
|
|
import com.google.common.base.Joiner;
|
|
import com.google.common.base.Splitter;
|
|
import com.google.common.collect.ImmutableMap;
|
|
import com.google.common.collect.ImmutableSortedMap;
|
|
import com.google.common.collect.Iterables;
|
|
import com.google.common.collect.Ordering;
|
|
import com.google.common.collect.Sets;
|
|
import com.google.common.primitives.Primitives;
|
|
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
|
|
import javax.annotation.Nullable;
|
|
|
|
/** Helper class for diff utilities. */
|
|
public final class DiffUtils {
|
|
|
|
/**
|
|
* A helper class to store the two sides of a diff. If both sides are Sets then they will be
|
|
* diffed, otherwise the two objects are toStringed in Collection format "[a, b]".
|
|
*/
|
|
private static class DiffPair {
|
|
@Nullable
|
|
final Object a;
|
|
|
|
@Nullable
|
|
final Object b;
|
|
|
|
DiffPair(@Nullable Object a, @Nullable Object b) {
|
|
this.a = a;
|
|
this.b = b;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
// Note that we use newArrayList here instead of ImmutableList because a and b can be null.
|
|
return newArrayList(a, b).toString();
|
|
}
|
|
}
|
|
|
|
/** Pretty-prints a deep diff between two maps. */
|
|
public static String prettyPrintDeepDiff(Map<?, ?> a, Map<?, ?> b) {
|
|
return prettyPrintDiffedMap(deepDiff(a, b), null);
|
|
}
|
|
|
|
/**
|
|
* Pretty-prints a deep diff between two maps. Path is prefixed to each output line of the diff.
|
|
*/
|
|
public static String prettyPrintDeepDiff(Map<?, ?> a, Map<?, ?> b, @Nullable String path) {
|
|
return prettyPrintDiffedMap(deepDiff(a, b), path);
|
|
}
|
|
|
|
/** Compare two maps and return a map containing, at each key where they differed, both values. */
|
|
public static ImmutableMap<?, ?> deepDiff(Map<?, ?> a, Map<?, ?> b) {
|
|
ImmutableMap.Builder<Object, Object> diff = new ImmutableMap.Builder<>();
|
|
for (Object key : Sets.union(a.keySet(), b.keySet())) {
|
|
Object aValue = a.get(key);
|
|
Object bValue = b.get(key);
|
|
if (!Objects.equals(aValue, bValue)) {
|
|
if (aValue instanceof String && bValue instanceof String
|
|
&& a.toString().contains("\n") && b.toString().contains("\n")) {
|
|
aValue = stringToMap((String) aValue);
|
|
bValue = stringToMap((String) bValue);
|
|
} else if (aValue instanceof Set && bValue instanceof Set) {
|
|
// Leave Sets alone; prettyPrintDiffedMap has special handling for Sets.
|
|
} else if (aValue instanceof Iterable && bValue instanceof Iterable) {
|
|
aValue = iterableToSortedMap((Iterable<?>) aValue);
|
|
bValue = iterableToSortedMap((Iterable<?>) bValue);
|
|
}
|
|
diff.put(key, (aValue instanceof Map && bValue instanceof Map)
|
|
? deepDiff((Map<?, ?>) aValue, (Map<?, ?>) bValue)
|
|
: new DiffPair(aValue, bValue));
|
|
}
|
|
}
|
|
return diff.build();
|
|
}
|
|
|
|
private static Map<Integer, ?> iterableToSortedMap(Iterable<?> iterable) {
|
|
// We use a sorted map here so that the iteration across the keySet is consistent.
|
|
ImmutableSortedMap.Builder<Integer, Object> builder =
|
|
new ImmutableSortedMap.Builder<>(Ordering.natural());
|
|
int i = 0;
|
|
for (Object item : Iterables.filter(iterable, notNull())) {
|
|
builder.put(i++, item);
|
|
}
|
|
return builder.build();
|
|
}
|
|
|
|
private static Map<String, ?> stringToMap(String string) {
|
|
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
|
int i = 0;
|
|
for (String item : Splitter.on('\n').split(string)) {
|
|
builder.put("Line " + i++, item);
|
|
}
|
|
return builder.build();
|
|
}
|
|
|
|
/** Recursively pretty prints the contents of a diffed map generated by {@link #deepDiff}. */
|
|
public static String prettyPrintDiffedMap(Map<?, ?> map, @Nullable String path) {
|
|
StringBuilder builder = new StringBuilder();
|
|
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
|
String newPath = (path == null ? "" : path + ".") + entry.getKey();
|
|
String output;
|
|
Object value = entry.getValue();
|
|
if (value instanceof Map) {
|
|
output = prettyPrintDiffedMap((Map<?, ?>) entry.getValue(), newPath);
|
|
} else if (value instanceof DiffPair
|
|
&& ((DiffPair) value).a instanceof Set
|
|
&& ((DiffPair) value).b instanceof Set) {
|
|
DiffPair pair = ((DiffPair) value);
|
|
String prettyLineDiff = prettyPrintSetDiff((Set<?>) pair.a, (Set<?>) pair.b) + "\n";
|
|
output = newPath + ((prettyLineDiff.startsWith("\n")) ? " ->" : " -> ") + prettyLineDiff;
|
|
} else {
|
|
output = newPath + " -> " + value + "\n";
|
|
}
|
|
builder.append(output);
|
|
}
|
|
return builder.toString();
|
|
}
|
|
|
|
/**
|
|
* Returns a string displaying the differences between the old values in a set and the new ones.
|
|
*/
|
|
@VisibleForTesting
|
|
static String prettyPrintSetDiff(Set<?> a, Set<?> b) {
|
|
Set<?> removed = Sets.difference(a, b);
|
|
Set<?> added = Sets.difference(b, a);
|
|
if (removed.isEmpty() && added.isEmpty()) {
|
|
return "NO DIFFERENCES";
|
|
}
|
|
return Joiner.on("\n ").skipNulls().join("",
|
|
!added.isEmpty() ? ("ADDED:" + formatSetContents(added)) : null,
|
|
!removed.isEmpty() ? ("REMOVED:" + formatSetContents(removed)) : null,
|
|
"FINAL CONTENTS:" + formatSetContents(b));
|
|
}
|
|
|
|
/**
|
|
* Returns a formatted listing of Set contents, using a single line format if all elements are
|
|
* wrappers of primitive types or Strings, and a multiline (one object per line) format if they
|
|
* are not.
|
|
*/
|
|
private static String formatSetContents(Set<?> set) {
|
|
for (Object obj : set) {
|
|
if (!Primitives.isWrapperType(obj.getClass()) && !(obj instanceof String)) {
|
|
return "\n " + Joiner.on(",\n ").join(set);
|
|
}
|
|
}
|
|
return " " + set;
|
|
}
|
|
|
|
private DiffUtils() {}
|
|
}
|