//
// Copyright © 2014, David Tesler (https://github.com/protobufel)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the <organization> nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
package com.github.protobufel.test.util;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.isA;
import static org.powermock.api.mockito.PowerMockito.mock;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.SortedMap;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.core.classloader.annotations.PrepareForTest;
import com.google.common.collect.Maps;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
@PrepareForTest({FieldDescriptor.class, Descriptor.class})
public class MockDescriptorCache {
private static final class MockDescriptorEntry {
private final Descriptor descriptor;
private final SortedMap<String, FieldDescriptor> fields;
public MockDescriptorEntry(Descriptor descriptor) {
this(descriptor, Maps.<String, FieldDescriptor>newTreeMap());
}
private MockDescriptorEntry(Descriptor descriptor, SortedMap<String, FieldDescriptor> fields) {
if ((descriptor == null) || (fields == null)) {
throw new NullPointerException();
}
this.descriptor = descriptor;
this.fields = fields;
}
public Descriptor getDescriptor() {
return descriptor;
}
public SortedMap<String, FieldDescriptor> getFields() {
return fields;
}
public FieldDescriptor getField(String fieldName) {
return fields.get(fieldName);
}
public FieldDescriptor getField(int fieldIndex) {
for (FieldDescriptor field : fields.values()) {
if (field.getIndex() == fieldIndex) {
return field;
}
}
return null;
}
public FieldDescriptor putField(String fieldName, FieldDescriptor field) {
return fields.put(fieldName, field);
}
@Override
public int hashCode() {
return Objects.hash(descriptor.getFullName(), fields);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof MockDescriptorEntry)) {
return false;
}
MockDescriptorEntry other = (MockDescriptorEntry) obj;
if (descriptor == null) {
if (other.descriptor != null) {
return false;
}
} else if (!descriptor.getFullName().equals(other.descriptor.getFullName())) {
return false;
}
if (fields == null) {
if (other.fields != null) {
return false;
}
} else if (!fields.equals(other.fields)) {
return false;
}
return true;
}
}
private final Map<String, MockDescriptorEntry> descriptorMap;
private final String basePath;
public MockDescriptorCache(Class<?> classForBasePath) {
this(classForBasePath.getName());
}
public MockDescriptorCache(String basePath) {
this(new HashMap<String, MockDescriptorEntry>(), basePath);
}
private MockDescriptorCache(Map<String, MockDescriptorEntry> descriptorMap,
String basePath) {
this.descriptorMap = descriptorMap;
this.basePath = basePath + ".";
}
public FieldDescriptor getField(String descriptorShortName, String fieldShortName) {
String descriptorFullName = getFullName(descriptorShortName);
MockDescriptorEntry fieldMap = descriptorMap.get(descriptorFullName);
if (fieldMap == null) {
return null;
}
return fieldMap.getField(fieldShortName);
}
public FieldDescriptor getField(String descriptorShortName, int index) {
String descriptorFullName = getFullName(descriptorShortName);
MockDescriptorEntry fieldMap = descriptorMap.get(descriptorFullName);
if (fieldMap == null) {
return null;
}
return fieldMap.getField(index);
}
public FieldDescriptor putField(String descriptorShortName, String fieldShortName,
int index, boolean isRepeated, JavaType type, String fieldMessageTypeName) {
String descriptorFullName = getFullName(descriptorShortName);
MockDescriptorEntry fieldMap = descriptorMap.get(descriptorFullName);
FieldDescriptor field = null;
if (fieldMap == null) {
fieldMap = new MockDescriptorEntry(createMockDescriptor(descriptorShortName));
descriptorMap.put(descriptorFullName, fieldMap);
} else {
field = fieldMap.getField(fieldShortName);
}
if (field == null) {
field = createMockField(fieldShortName, index, isRepeated, type,
fieldMap.getDescriptor(), fieldMessageTypeName);
fieldMap.putField(fieldShortName, field);
}
return field;
}
private String getFullName(String shortName) {
return basePath + shortName;
}
private FieldDescriptor createMockField(String name, int index, boolean isRepeated,
JavaType type, Descriptor parent, String fieldMessageTypeName) {
return createMockField(fieldMessageTypeName, index, isRepeated, type, parent,
putDescriptor(fieldMessageTypeName));
}
public Descriptor putDescriptor(String descriptorName) {
Descriptor descriptor = getDescriptor(descriptorName);
if (descriptor == null) {
descriptor = createMockDescriptor(descriptorName);
descriptorMap.put(descriptorName, new MockDescriptorEntry(descriptor));
}
return descriptor;
}
public Descriptor getDescriptor(String decsriptorFullName) {
MockDescriptorEntry entry = descriptorMap.get(decsriptorFullName);
return (entry == null) ? null : entry.getDescriptor();
}
public FieldDescriptor createMockField(String name, int index, boolean isRepeated,
JavaType type, Descriptor parent, Descriptor fieldMessageType) {
FieldDescriptor field = mock(FieldDescriptor.class);
//given
given(field.isRepeated()).willReturn(isRepeated);
given(field.getJavaType()).willReturn(type);
given(field.getContainingType()).willReturn(parent);
given(field.getName()).willReturn(parent.getFullName() + "." + name);
given(field.getIndex()).willReturn(index);
given(field.getMessageType()).willReturn((type == JavaType.MESSAGE) ? fieldMessageType : null);
given(field.compareTo(isA(FieldDescriptor.class))).willAnswer(new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocation) throws Throwable {
FieldDescriptor other = (FieldDescriptor) invocation.getArguments()[0];
FieldDescriptor mock = (FieldDescriptor) invocation.getMock();
return Integer.compare(mock.getNumber(), other.getNumber());
}
});
return field;
}
@SuppressWarnings("unchecked")
public Descriptor createMockDescriptor(final String name) {
Descriptor descriptor = mock(Descriptor.class);
final String fullName = getFullName(name);
//given
given(descriptor.getFullName()).willReturn(fullName);
given(descriptor.findFieldByName(anyString())).willAnswer(new Answer<FieldDescriptor>() {
@Override
public FieldDescriptor answer(InvocationOnMock invocation) throws Throwable {
// Descriptor mock = (Descriptor) invocation.getMock();
return getField(fullName, (String) invocation.getArguments()[0]);
}
});
given(descriptor.findFieldByNumber(anyInt())).willAnswer(new Answer<FieldDescriptor>(){
@Override
public FieldDescriptor answer(InvocationOnMock invocation) throws Throwable {
// Descriptor mock = (Descriptor) invocation.getMock();
return getField(fullName, (Integer) invocation.getArguments()[0]);
}});
given(descriptor.findEnumTypeByName(fullName)).willThrow(UnsupportedOperationException.class);
return descriptor;
}
}