blob: 369e21a8e3e5a1a50911da836f1f9e48004553fd [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.apache.wave.pst.testing;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.protobuf.ByteString;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.EnumDescriptor;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.GeneratedMessage;
import com.google.protobuf.Message.Builder;
import java.util.List;
import java.util.Map;
import java.util.Random;
/**
* Generates random protocol buffers with all fields given a value.
*
* @author kalman@google.com (Benjamin Kalman)
*/
public final class RandomProtobufGenerator<E extends GeneratedMessage> {
private static final int MAX_LIST_LENGTH = 10;
private static final int MAX_STRING_LENGTH = 10;
private final Random random;
private final GeneratedMessage instance;
private final Map<Descriptor, GeneratedMessage> moreInstances;
private RandomProtobufGenerator(Random random, GeneratedMessage instance,
Map<Descriptor, GeneratedMessage> moreInstances) {
this.random = random;
this.instance = instance;
this.moreInstances = moreInstances;
}
/**
* Creates a random protobuf generator, for a main type (instance) and a list
* of any extra instances that will be needed to generate that instance.
*
* @param random random number generator
* @param instance the protobuf instance used as a template to generate more
* random protobuf
* @param moreInstances protobuf templates for any inner messages the
* protobuf depends on
* @return the random protobuf generator
*/
public static <T extends GeneratedMessage> RandomProtobufGenerator<T> create(Random random,
GeneratedMessage instance, GeneratedMessage... moreInstances) {
// NOTE: it would be nice to determine this internally e.g. through (java or proto) reflection.
Map<Descriptor, GeneratedMessage> moreInstancesMap = Maps.newHashMap();
for (GeneratedMessage gm : moreInstances) {
moreInstancesMap.put(gm.getDescriptorForType(), gm);
}
return new RandomProtobufGenerator<T>(random, instance, moreInstancesMap);
}
/**
* Generates a random protocol buffer, filling in all fields and giving
* repeated values 0..n items.
*/
public E generate() {
return generate(0);
}
/**
* Generates a random protocol buffer, filling in all required fields but
* with a p chance of not setting an optional field and p chance of having
* an empty repeated field.
*/
@SuppressWarnings("unchecked")
public E generate(double p) {
Builder builder = instance.newBuilderForType();
Descriptor descriptor = instance.getDescriptorForType();
for (FieldDescriptor field : descriptor.getFields()) {
if (!field.isRequired() && random.nextDouble() < p) {
continue;
}
builder.setField(field, getRandomValue(field, p));
}
return (E) builder.build();
}
private Object getRandomValue(FieldDescriptor field, double p) {
if (field.isRepeated()) {
List<Object> values = Lists.newArrayList();
for (int i = 0, length = random.nextInt(MAX_LIST_LENGTH); i < length; i++) {
values.add(getRandomSingleValue(field, p));
}
return values;
} else {
return getRandomSingleValue(field, p);
}
}
private Object getRandomSingleValue(FieldDescriptor field, double p) {
switch (field.getJavaType()) {
case BOOLEAN:
return random.nextBoolean();
case BYTE_STRING:
return getRandomByteString();
case DOUBLE:
return random.nextDouble();
case ENUM:
return getRandomEnum(field.getEnumType());
case FLOAT:
return random.nextFloat();
case INT:
return random.nextInt();
case LONG:
return random.nextLong();
case MESSAGE:
return getRandomMessage(field, p);
case STRING:
return getRandomString();
default:
return null;
}
}
private ByteString getRandomByteString() {
byte[] bytes = new byte[32];
random.nextBytes(bytes);
return ByteString.copyFrom(bytes);
}
private EnumValueDescriptor getRandomEnum(EnumDescriptor enumD) {
List<EnumValueDescriptor> values = enumD.getValues();
return values.get(random.nextInt(values.size()));
}
private GeneratedMessage getRandomMessage(FieldDescriptor field, double p) {
GeneratedMessage instance = moreInstances.get(field.getMessageType());
if (instance == null) {
throw new IllegalArgumentException("Couldn't find instance for message "
+ field.getMessageType().getFullName());
}
return new RandomProtobufGenerator<GeneratedMessage>(random, instance, moreInstances)
.generate(p);
}
private String getRandomString() {
String alphabet = "abc{}[]<>\\\"'";
StringBuilder s = new StringBuilder();
for (int i = 0, length = random.nextInt(MAX_STRING_LENGTH); i < length; i++) {
s.append(alphabet.charAt(random.nextInt(alphabet.length())));
}
return s.toString();
}
}