class Utils {

    public static String computeSignature(ServiceDocument s, ServiceDocumentDescription description) {
        if (description == null) {
            throw new IllegalArgumentException("description is required");
        }

        long hash = FNVHash.FNV_OFFSET_MINUS_MSB;

        for (PropertyDescription pd : description.propertyDescriptions.values()) {
            if (pd.indexingOptions != null) {
                if (pd.indexingOptions.contains(PropertyIndexingOption.EXCLUDE_FROM_SIGNATURE)) {
                    continue;
                }
            }

            // Calculate hash of all non-excluded fields, to make sure the following:
            // {a='1', b=null} != {a=null, b='1'}
            Object fieldValue = ReflectionUtils.getPropertyValue(pd, s);
            if (fieldValue == null) {
                hash = FNVHash.compute(-1, hash);
                continue;
            }

            switch (pd.typeName) {
            case BYTES:
                // special case for bytes to avoid base64 encoding
                byte[] bytes = (byte[]) fieldValue;
                hash = FNVHash.compute(bytes, 0, bytes.length, hash);
                break;
            default:
                hash = GsonSerializers.hashJson(fieldValue, hash);
            }
        }

        return Long.toHexString(hash);
    }

    private  Utils();

}