blob: c879bdb4a1d5e31c74305f99aee406faca2fddec [file] [log] [blame]
/*
* Copyright 2015-present Facebook, Inc.
*
* 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.facebook.buck.json;
import com.google.common.base.Charsets;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.hash.Hasher;
import java.util.Collection;
import java.util.Map;
import javax.annotation.Nullable;
/**
* Hashes parsed JSON objects as returned by {@link RawParser}.
*/
public class JsonObjectHashing {
private static enum HashedObjectType {
BOOLEAN,
DOUBLE,
FLOAT,
INTEGER,
LIST,
LONG,
SHORT,
STRING,
MAP,
NULL
}
// Utility class; do not instantiate.
private JsonObjectHashing() { }
/**
* Given a {@link Hasher} and a parsed JSON object returned by
* {@link RawParser#parseFromReader(java.io.Reader)}, updates the Hasher
* with the contents of the JSON object.
*/
public static void hashJsonObject(Hasher hasher, @Nullable Object obj) {
if (obj instanceof Map) {
Map<?, ?> map = (Map<?, ?>) obj;
ImmutableSortedMap.Builder<String, Optional<Object>> sortedMapBuilder =
ImmutableSortedMap.naturalOrder();
for (Map.Entry<?, ?> entry : map.entrySet()) {
Object key = entry.getKey();
if (!(key instanceof String)) {
throw new RuntimeException(
String.format(
"Keys of JSON maps are expected to be strings. Actual type: %s, contents: %s",
key.getClass().getName(),
key));
}
Object value = entry.getValue();
if (value != null) {
sortedMapBuilder.put((String) key, Optional.of(value));
} else {
sortedMapBuilder.put((String) key, Optional.absent());
}
}
ImmutableSortedMap<String, Optional<Object>> sortedMap = sortedMapBuilder.build();
hasher.putInt(HashedObjectType.MAP.ordinal());
hasher.putInt(sortedMap.size());
for (Map.Entry<String, Optional<Object>> entry : sortedMap.entrySet()) {
hashJsonObject(hasher, entry.getKey());
if (entry.getValue().isPresent()) {
hashJsonObject(hasher, entry.getValue().get());
} else {
hashJsonObject(hasher, null);
}
}
} else if (obj instanceof Collection) {
Collection<?> collection = (Collection<?>) obj;
hasher.putInt(HashedObjectType.LIST.ordinal());
hasher.putInt(collection.size());
for (Object collectionEntry : collection) {
hashJsonObject(hasher, collectionEntry);
}
} else if (obj instanceof String) {
hasher.putInt(HashedObjectType.STRING.ordinal());
byte[] stringBytes = ((String) obj).getBytes(Charsets.UTF_8);
hasher.putInt(stringBytes.length);
hasher.putBytes(stringBytes);
} else if (obj instanceof Boolean) {
hasher.putInt(HashedObjectType.BOOLEAN.ordinal());
hasher.putBoolean((boolean) obj);
} else if (obj instanceof Number) {
// This is gross, but it mimics the logic in RawParser.
Number number = (Number) obj;
if (number.longValue() == number.doubleValue()) {
hasher.putInt(HashedObjectType.LONG.ordinal());
hasher.putLong(number.longValue());
} else {
hasher.putInt(HashedObjectType.DOUBLE.ordinal());
hasher.putDouble(number.doubleValue());
}
} else if (obj instanceof Void || obj == null) {
hasher.putInt(HashedObjectType.NULL.ordinal());
} else {
throw new RuntimeException(
String.format("Unsupported object %s (class %s)", obj, obj.getClass()));
}
}
}