// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
// http://code.google.com/p/protobuf/
//
// 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 Google Inc. 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 THE COPYRIGHT
// OWNER OR CONTRIBUTORS 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.google.protobuf;

import com.google.protobuf.DescriptorProtos.DescriptorProto;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
import com.google.protobuf.Descriptors.DescriptorValidationException;
import com.google.protobuf.Descriptors.FileDescriptor;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.EnumDescriptor;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.ServiceDescriptor;
import com.google.protobuf.Descriptors.MethodDescriptor;

import com.google.protobuf.test.UnittestImport;
import com.google.protobuf.test.UnittestImport.ImportEnum;
import com.google.protobuf.test.UnittestImport.ImportMessage;
import protobuf_unittest.UnittestProto;
import protobuf_unittest.UnittestProto.ForeignEnum;
import protobuf_unittest.UnittestProto.ForeignMessage;
import protobuf_unittest.UnittestProto.TestAllTypes;
import protobuf_unittest.UnittestProto.TestAllExtensions;
import protobuf_unittest.UnittestProto.TestExtremeDefaultValues;
import protobuf_unittest.UnittestProto.TestRequired;
import protobuf_unittest.UnittestProto.TestService;
import protobuf_unittest.UnittestCustomOptions;


import junit.framework.TestCase;

import java.util.Arrays;
import java.util.Collections;

/**
 * Unit test for {@link Descriptors}.
 *
 * @author kenton@google.com Kenton Varda
 */
public class DescriptorsTest extends TestCase {

  // Regression test for bug where referencing a FieldDescriptor.Type value
  // before a FieldDescriptorProto.Type value would yield a
  // ExceptionInInitializerError.
  private static final Object STATIC_INIT_TEST = FieldDescriptor.Type.BOOL;

  public void testFieldTypeEnumMapping() throws Exception {
    assertEquals(FieldDescriptor.Type.values().length,
        FieldDescriptorProto.Type.values().length);
    for (FieldDescriptor.Type type : FieldDescriptor.Type.values()) {
      FieldDescriptorProto.Type protoType = type.toProto();
      assertEquals("TYPE_" + type.name(), protoType.name());
      assertEquals(type, FieldDescriptor.Type.valueOf(protoType));
    }
  }

  public void testFileDescriptor() throws Exception {
    FileDescriptor file = UnittestProto.getDescriptor();

    assertEquals("google/protobuf/unittest.proto", file.getName());
    assertEquals("protobuf_unittest", file.getPackage());

    assertEquals("UnittestProto", file.getOptions().getJavaOuterClassname());
    assertEquals("google/protobuf/unittest.proto",
                 file.toProto().getName());

    assertEquals(Arrays.asList(UnittestImport.getDescriptor()),
                 file.getDependencies());

    Descriptor messageType = TestAllTypes.getDescriptor();
    assertEquals(messageType, file.getMessageTypes().get(0));
    assertEquals(messageType, file.findMessageTypeByName("TestAllTypes"));
    assertNull(file.findMessageTypeByName("NoSuchType"));
    assertNull(file.findMessageTypeByName("protobuf_unittest.TestAllTypes"));
    for (int i = 0; i < file.getMessageTypes().size(); i++) {
      assertEquals(i, file.getMessageTypes().get(i).getIndex());
    }

    EnumDescriptor enumType = ForeignEnum.getDescriptor();
    assertEquals(enumType, file.getEnumTypes().get(0));
    assertEquals(enumType, file.findEnumTypeByName("ForeignEnum"));
    assertNull(file.findEnumTypeByName("NoSuchType"));
    assertNull(file.findEnumTypeByName("protobuf_unittest.ForeignEnum"));
    assertEquals(Arrays.asList(ImportEnum.getDescriptor()),
                 UnittestImport.getDescriptor().getEnumTypes());
    for (int i = 0; i < file.getEnumTypes().size(); i++) {
      assertEquals(i, file.getEnumTypes().get(i).getIndex());
    }

    ServiceDescriptor service = TestService.getDescriptor();
    assertEquals(service, file.getServices().get(0));
    assertEquals(service, file.findServiceByName("TestService"));
    assertNull(file.findServiceByName("NoSuchType"));
    assertNull(file.findServiceByName("protobuf_unittest.TestService"));
    assertEquals(Collections.emptyList(),
                 UnittestImport.getDescriptor().getServices());
    for (int i = 0; i < file.getServices().size(); i++) {
      assertEquals(i, file.getServices().get(i).getIndex());
    }

    FieldDescriptor extension =
      UnittestProto.optionalInt32Extension.getDescriptor();
    assertEquals(extension, file.getExtensions().get(0));
    assertEquals(extension,
                 file.findExtensionByName("optional_int32_extension"));
    assertNull(file.findExtensionByName("no_such_ext"));
    assertNull(file.findExtensionByName(
      "protobuf_unittest.optional_int32_extension"));
    assertEquals(Collections.emptyList(),
                 UnittestImport.getDescriptor().getExtensions());
    for (int i = 0; i < file.getExtensions().size(); i++) {
      assertEquals(i, file.getExtensions().get(i).getIndex());
    }
  }

  public void testDescriptor() throws Exception {
    Descriptor messageType = TestAllTypes.getDescriptor();
    Descriptor nestedType = TestAllTypes.NestedMessage.getDescriptor();

    assertEquals("TestAllTypes", messageType.getName());
    assertEquals("protobuf_unittest.TestAllTypes", messageType.getFullName());
    assertEquals(UnittestProto.getDescriptor(), messageType.getFile());
    assertNull(messageType.getContainingType());
    assertEquals(DescriptorProtos.MessageOptions.getDefaultInstance(),
                 messageType.getOptions());
    assertEquals("TestAllTypes", messageType.toProto().getName());

    assertEquals("NestedMessage", nestedType.getName());
    assertEquals("protobuf_unittest.TestAllTypes.NestedMessage",
                 nestedType.getFullName());
    assertEquals(UnittestProto.getDescriptor(), nestedType.getFile());
    assertEquals(messageType, nestedType.getContainingType());

    FieldDescriptor field = messageType.getFields().get(0);
    assertEquals("optional_int32", field.getName());
    assertEquals(field, messageType.findFieldByName("optional_int32"));
    assertNull(messageType.findFieldByName("no_such_field"));
    assertEquals(field, messageType.findFieldByNumber(1));
    assertNull(messageType.findFieldByNumber(571283));
    for (int i = 0; i < messageType.getFields().size(); i++) {
      assertEquals(i, messageType.getFields().get(i).getIndex());
    }

    assertEquals(nestedType, messageType.getNestedTypes().get(0));
    assertEquals(nestedType, messageType.findNestedTypeByName("NestedMessage"));
    assertNull(messageType.findNestedTypeByName("NoSuchType"));
    for (int i = 0; i < messageType.getNestedTypes().size(); i++) {
      assertEquals(i, messageType.getNestedTypes().get(i).getIndex());
    }

    EnumDescriptor enumType = TestAllTypes.NestedEnum.getDescriptor();
    assertEquals(enumType, messageType.getEnumTypes().get(0));
    assertEquals(enumType, messageType.findEnumTypeByName("NestedEnum"));
    assertNull(messageType.findEnumTypeByName("NoSuchType"));
    for (int i = 0; i < messageType.getEnumTypes().size(); i++) {
      assertEquals(i, messageType.getEnumTypes().get(i).getIndex());
    }
  }

  public void testFieldDescriptor() throws Exception {
    Descriptor messageType = TestAllTypes.getDescriptor();
    FieldDescriptor primitiveField =
      messageType.findFieldByName("optional_int32");
    FieldDescriptor enumField =
      messageType.findFieldByName("optional_nested_enum");
    FieldDescriptor messageField =
      messageType.findFieldByName("optional_foreign_message");
    FieldDescriptor cordField =
      messageType.findFieldByName("optional_cord");
    FieldDescriptor extension =
      UnittestProto.optionalInt32Extension.getDescriptor();
    FieldDescriptor nestedExtension = TestRequired.single.getDescriptor();

    assertEquals("optional_int32", primitiveField.getName());
    assertEquals("protobuf_unittest.TestAllTypes.optional_int32",
                 primitiveField.getFullName());
    assertEquals(1, primitiveField.getNumber());
    assertEquals(messageType, primitiveField.getContainingType());
    assertEquals(UnittestProto.getDescriptor(), primitiveField.getFile());
    assertEquals(FieldDescriptor.Type.INT32, primitiveField.getType());
    assertEquals(FieldDescriptor.JavaType.INT, primitiveField.getJavaType());
    assertEquals(DescriptorProtos.FieldOptions.getDefaultInstance(),
                 primitiveField.getOptions());
    assertFalse(primitiveField.isExtension());
    assertEquals("optional_int32", primitiveField.toProto().getName());

    assertEquals("optional_nested_enum", enumField.getName());
    assertEquals(FieldDescriptor.Type.ENUM, enumField.getType());
    assertEquals(FieldDescriptor.JavaType.ENUM, enumField.getJavaType());
    assertEquals(TestAllTypes.NestedEnum.getDescriptor(),
                 enumField.getEnumType());

    assertEquals("optional_foreign_message", messageField.getName());
    assertEquals(FieldDescriptor.Type.MESSAGE, messageField.getType());
    assertEquals(FieldDescriptor.JavaType.MESSAGE, messageField.getJavaType());
    assertEquals(ForeignMessage.getDescriptor(), messageField.getMessageType());

    assertEquals("optional_cord", cordField.getName());
    assertEquals(FieldDescriptor.Type.STRING, cordField.getType());
    assertEquals(FieldDescriptor.JavaType.STRING, cordField.getJavaType());
    assertEquals(DescriptorProtos.FieldOptions.CType.CORD,
                 cordField.getOptions().getCtype());

    assertEquals("optional_int32_extension", extension.getName());
    assertEquals("protobuf_unittest.optional_int32_extension",
                 extension.getFullName());
    assertEquals(1, extension.getNumber());
    assertEquals(TestAllExtensions.getDescriptor(),
                 extension.getContainingType());
    assertEquals(UnittestProto.getDescriptor(), extension.getFile());
    assertEquals(FieldDescriptor.Type.INT32, extension.getType());
    assertEquals(FieldDescriptor.JavaType.INT, extension.getJavaType());
    assertEquals(DescriptorProtos.FieldOptions.getDefaultInstance(),
                 extension.getOptions());
    assertTrue(extension.isExtension());
    assertEquals(null, extension.getExtensionScope());
    assertEquals("optional_int32_extension", extension.toProto().getName());

    assertEquals("single", nestedExtension.getName());
    assertEquals("protobuf_unittest.TestRequired.single",
                 nestedExtension.getFullName());
    assertEquals(TestRequired.getDescriptor(),
                 nestedExtension.getExtensionScope());
  }

  public void testFieldDescriptorLabel() throws Exception {
    FieldDescriptor requiredField =
      TestRequired.getDescriptor().findFieldByName("a");
    FieldDescriptor optionalField =
      TestAllTypes.getDescriptor().findFieldByName("optional_int32");
    FieldDescriptor repeatedField =
      TestAllTypes.getDescriptor().findFieldByName("repeated_int32");

    assertTrue(requiredField.isRequired());
    assertFalse(requiredField.isRepeated());
    assertFalse(optionalField.isRequired());
    assertFalse(optionalField.isRepeated());
    assertFalse(repeatedField.isRequired());
    assertTrue(repeatedField.isRepeated());
  }

  public void testFieldDescriptorDefault() throws Exception {
    Descriptor d = TestAllTypes.getDescriptor();
    assertFalse(d.findFieldByName("optional_int32").hasDefaultValue());
    assertEquals(0, d.findFieldByName("optional_int32").getDefaultValue());
    assertTrue(d.findFieldByName("default_int32").hasDefaultValue());
    assertEquals(41, d.findFieldByName("default_int32").getDefaultValue());

    d = TestExtremeDefaultValues.getDescriptor();
    assertEquals(
      ByteString.copyFrom(
        "\0\001\007\b\f\n\r\t\013\\\'\"\u00fe".getBytes("ISO-8859-1")),
      d.findFieldByName("escaped_bytes").getDefaultValue());
    assertEquals(-1, d.findFieldByName("large_uint32").getDefaultValue());
    assertEquals(-1L, d.findFieldByName("large_uint64").getDefaultValue());
  }

  public void testEnumDescriptor() throws Exception {
    EnumDescriptor enumType = ForeignEnum.getDescriptor();
    EnumDescriptor nestedType = TestAllTypes.NestedEnum.getDescriptor();

    assertEquals("ForeignEnum", enumType.getName());
    assertEquals("protobuf_unittest.ForeignEnum", enumType.getFullName());
    assertEquals(UnittestProto.getDescriptor(), enumType.getFile());
    assertNull(enumType.getContainingType());
    assertEquals(DescriptorProtos.EnumOptions.getDefaultInstance(),
                 enumType.getOptions());

    assertEquals("NestedEnum", nestedType.getName());
    assertEquals("protobuf_unittest.TestAllTypes.NestedEnum",
                 nestedType.getFullName());
    assertEquals(UnittestProto.getDescriptor(), nestedType.getFile());
    assertEquals(TestAllTypes.getDescriptor(), nestedType.getContainingType());

    EnumValueDescriptor value = ForeignEnum.FOREIGN_FOO.getValueDescriptor();
    assertEquals(value, enumType.getValues().get(0));
    assertEquals("FOREIGN_FOO", value.getName());
    assertEquals(4, value.getNumber());
    assertEquals(value, enumType.findValueByName("FOREIGN_FOO"));
    assertEquals(value, enumType.findValueByNumber(4));
    assertNull(enumType.findValueByName("NO_SUCH_VALUE"));
    for (int i = 0; i < enumType.getValues().size(); i++) {
      assertEquals(i, enumType.getValues().get(i).getIndex());
    }
  }

  public void testServiceDescriptor() throws Exception {
    ServiceDescriptor service = TestService.getDescriptor();

    assertEquals("TestService", service.getName());
    assertEquals("protobuf_unittest.TestService", service.getFullName());
    assertEquals(UnittestProto.getDescriptor(), service.getFile());

    assertEquals(2, service.getMethods().size());

    MethodDescriptor fooMethod = service.getMethods().get(0);
    assertEquals("Foo", fooMethod.getName());
    assertEquals(UnittestProto.FooRequest.getDescriptor(),
                 fooMethod.getInputType());
    assertEquals(UnittestProto.FooResponse.getDescriptor(),
                 fooMethod.getOutputType());
    assertEquals(fooMethod, service.findMethodByName("Foo"));

    MethodDescriptor barMethod = service.getMethods().get(1);
    assertEquals("Bar", barMethod.getName());
    assertEquals(UnittestProto.BarRequest.getDescriptor(),
                 barMethod.getInputType());
    assertEquals(UnittestProto.BarResponse.getDescriptor(),
                 barMethod.getOutputType());
    assertEquals(barMethod, service.findMethodByName("Bar"));

    assertNull(service.findMethodByName("NoSuchMethod"));

    for (int i = 0; i < service.getMethods().size(); i++) {
      assertEquals(i, service.getMethods().get(i).getIndex());
    }
  }


  public void testCustomOptions() throws Exception {
    Descriptor descriptor =
      UnittestCustomOptions.TestMessageWithCustomOptions.getDescriptor();

    assertTrue(
      descriptor.getOptions().hasExtension(UnittestCustomOptions.messageOpt1));
    assertEquals(Integer.valueOf(-56),
      descriptor.getOptions().getExtension(UnittestCustomOptions.messageOpt1));

    FieldDescriptor field = descriptor.findFieldByName("field1");
    assertNotNull(field);

    assertTrue(
      field.getOptions().hasExtension(UnittestCustomOptions.fieldOpt1));
    assertEquals(Long.valueOf(8765432109L),
      field.getOptions().getExtension(UnittestCustomOptions.fieldOpt1));

    EnumDescriptor enumType =
      UnittestCustomOptions.TestMessageWithCustomOptions.AnEnum.getDescriptor();

    assertTrue(
      enumType.getOptions().hasExtension(UnittestCustomOptions.enumOpt1));
    assertEquals(Integer.valueOf(-789),
      enumType.getOptions().getExtension(UnittestCustomOptions.enumOpt1));

    ServiceDescriptor service =
      UnittestCustomOptions.TestServiceWithCustomOptions.getDescriptor();

    assertTrue(
      service.getOptions().hasExtension(UnittestCustomOptions.serviceOpt1));
    assertEquals(Long.valueOf(-9876543210L),
      service.getOptions().getExtension(UnittestCustomOptions.serviceOpt1));

    MethodDescriptor method = service.findMethodByName("Foo");
    assertNotNull(method);

    assertTrue(
      method.getOptions().hasExtension(UnittestCustomOptions.methodOpt1));
    assertEquals(UnittestCustomOptions.MethodOpt1.METHODOPT1_VAL2,
      method.getOptions().getExtension(UnittestCustomOptions.methodOpt1));
  }

  /**
   * Test that the FieldDescriptor.Type enum is the same as the
   * WireFormat.FieldType enum.
   */
  public void testFieldTypeTablesMatch() throws Exception {
    FieldDescriptor.Type[] values1 = FieldDescriptor.Type.values();
    WireFormat.FieldType[] values2 = WireFormat.FieldType.values();

    assertEquals(values1.length, values2.length);

    for (int i = 0; i < values1.length; i++) {
      assertEquals(values1[i].toString(), values2[i].toString());
    }
  }

  /**
   * Test that the FieldDescriptor.JavaType enum is the same as the
   * WireFormat.JavaType enum.
   */
  public void testJavaTypeTablesMatch() throws Exception {
    FieldDescriptor.JavaType[] values1 = FieldDescriptor.JavaType.values();
    WireFormat.JavaType[] values2 = WireFormat.JavaType.values();

    assertEquals(values1.length, values2.length);

    for (int i = 0; i < values1.length; i++) {
      assertEquals(values1[i].toString(), values2[i].toString());
    }
  }

  public void testEnormousDescriptor() throws Exception {
    // The descriptor for this file is larger than 64k, yet it did not cause
    // a compiler error due to an over-long string literal.
    assertTrue(
        UnittestEnormousDescriptor.getDescriptor()
          .toProto().getSerializedSize() > 65536);
  }
  
  /**
   * Tests that the DescriptorValidationException works as intended.
   */
  public void testDescriptorValidatorException() throws Exception {
    FileDescriptorProto fileDescriptorProto = FileDescriptorProto.newBuilder()
      .setName("foo.proto")
      .addMessageType(DescriptorProto.newBuilder()
      .setName("Foo")
        .addField(FieldDescriptorProto.newBuilder()
          .setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL)
          .setType(FieldDescriptorProto.Type.TYPE_INT32)
          .setName("foo")
          .setNumber(1)
          .setDefaultValue("invalid")
          .build())
        .build())
      .build();
    try {
      Descriptors.FileDescriptor.buildFrom(fileDescriptorProto, 
          new FileDescriptor[0]);
      fail("DescriptorValidationException expected");
    } catch (DescriptorValidationException e) {
      // Expected; check that the error message contains some useful hints
      assertTrue(e.getMessage().indexOf("foo") != -1);
      assertTrue(e.getMessage().indexOf("Foo") != -1);
      assertTrue(e.getMessage().indexOf("invalid") != -1);
      assertTrue(e.getCause() instanceof NumberFormatException);
      assertTrue(e.getCause().getMessage().indexOf("invalid") != -1);
    }
  }
}
