// Copyright (c) 2013, Vastech SA (PTY) LTD. All rights reserved. // http://github.com/gogo/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. // // 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 compare import ( "github.com/gogo/protobuf/gogoproto" "github.com/gogo/protobuf/proto" descriptor "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" "github.com/gogo/protobuf/protoc-gen-gogo/generator" "github.com/gogo/protobuf/vanity" ) type plugin struct { *generator.Generator generator.PluginImports fmtPkg generator.Single bytesPkg generator.Single sortkeysPkg generator.Single } func NewPlugin() *plugin { return &plugin{} } func (p *plugin) Name() string { return "compare" } func (p *plugin) Init(g *generator.Generator) { p.Generator = g } func (p *plugin) Generate(file *generator.FileDescriptor) { p.PluginImports = generator.NewPluginImports(p.Generator) p.fmtPkg = p.NewImport("fmt") p.bytesPkg = p.NewImport("bytes") p.sortkeysPkg = p.NewImport("github.com/gogo/protobuf/sortkeys") for _, msg := range file.Messages() { if msg.DescriptorProto.GetOptions().GetMapEntry() { continue } if gogoproto.HasCompare(file.FileDescriptorProto, msg.DescriptorProto) { p.generateMessage(file, msg) } } } func (p *plugin) generateNullableField(fieldname string) { p.P(`if this.`, fieldname, ` != nil && that1.`, fieldname, ` != nil {`) p.In() p.P(`if *this.`, fieldname, ` != *that1.`, fieldname, `{`) p.In() p.P(`if *this.`, fieldname, ` < *that1.`, fieldname, `{`) p.In() p.P(`return -1`) p.Out() p.P(`}`) p.P(`return 1`) p.Out() p.P(`}`) p.Out() p.P(`} else if this.`, fieldname, ` != nil {`) p.In() p.P(`return 1`) p.Out() p.P(`} else if that1.`, fieldname, ` != nil {`) p.In() p.P(`return -1`) p.Out() p.P(`}`) } func (p *plugin) generateMsgNullAndTypeCheck(ccTypeName string) { p.P(`if that == nil {`) p.In() p.P(`if this == nil {`) p.In() p.P(`return 0`) p.Out() p.P(`}`) p.P(`return 1`) p.Out() p.P(`}`) p.P(``) p.P(`that1, ok := that.(*`, ccTypeName, `)`) p.P(`if !ok {`) p.In() p.P(`that2, ok := that.(`, ccTypeName, `)`) p.P(`if ok {`) p.In() p.P(`that1 = &that2`) p.Out() p.P(`} else {`) p.In() p.P(`return 1`) p.Out() p.P(`}`) p.Out() p.P(`}`) p.P(`if that1 == nil {`) p.In() p.P(`if this == nil {`) p.In() p.P(`return 0`) p.Out() p.P(`}`) p.P(`return 1`) p.Out() p.P(`} else if this == nil {`) p.In() p.P(`return -1`) p.Out() p.P(`}`) } func (p *plugin) generateField(file *generator.FileDescriptor, message *generator.Descriptor, field *descriptor.FieldDescriptorProto) { proto3 := gogoproto.IsProto3(file.FileDescriptorProto) fieldname := p.GetOneOfFieldName(message, field) repeated := field.IsRepeated() ctype := gogoproto.IsCustomType(field) nullable := gogoproto.IsNullable(field) // oneof := field.OneofIndex != nil if !repeated { if ctype { if nullable { p.P(`if that1.`, fieldname, ` == nil {`) p.In() p.P(`if this.`, fieldname, ` != nil {`) p.In() p.P(`return 1`) p.Out() p.P(`}`) p.Out() p.P(`} else if this.`, fieldname, ` == nil {`) p.In() p.P(`return -1`) p.Out() p.P(`} else if c := this.`, fieldname, `.Compare(*that1.`, fieldname, `); c != 0 {`) } else { p.P(`if c := this.`, fieldname, `.Compare(that1.`, fieldname, `); c != 0 {`) } p.In() p.P(`return c`) p.Out() p.P(`}`) } else { if field.IsMessage() || p.IsGroup(field) { if nullable { p.P(`if c := this.`, fieldname, `.Compare(that1.`, fieldname, `); c != 0 {`) } else { p.P(`if c := this.`, fieldname, `.Compare(&that1.`, fieldname, `); c != 0 {`) } p.In() p.P(`return c`) p.Out() p.P(`}`) } else if field.IsBytes() { p.P(`if c := `, p.bytesPkg.Use(), `.Compare(this.`, fieldname, `, that1.`, fieldname, `); c != 0 {`) p.In() p.P(`return c`) p.Out() p.P(`}`) } else if field.IsString() { if nullable && !proto3 { p.generateNullableField(fieldname) } else { p.P(`if this.`, fieldname, ` != that1.`, fieldname, `{`) p.In() p.P(`if this.`, fieldname, ` < that1.`, fieldname, `{`) p.In() p.P(`return -1`) p.Out() p.P(`}`) p.P(`return 1`) p.Out() p.P(`}`) } } else if field.IsBool() { if nullable && !proto3 { p.P(`if this.`, fieldname, ` != nil && that1.`, fieldname, ` != nil {`) p.In() p.P(`if *this.`, fieldname, ` != *that1.`, fieldname, `{`) p.In() p.P(`if !*this.`, fieldname, ` {`) p.In() p.P(`return -1`) p.Out() p.P(`}`) p.P(`return 1`) p.Out() p.P(`}`) p.Out() p.P(`} else if this.`, fieldname, ` != nil {`) p.In() p.P(`return 1`) p.Out() p.P(`} else if that1.`, fieldname, ` != nil {`) p.In() p.P(`return -1`) p.Out() p.P(`}`) } else { p.P(`if this.`, fieldname, ` != that1.`, fieldname, `{`) p.In() p.P(`if !this.`, fieldname, ` {`) p.In() p.P(`return -1`) p.Out() p.P(`}`) p.P(`return 1`) p.Out() p.P(`}`) } } else { if nullable && !proto3 { p.generateNullableField(fieldname) } else { p.P(`if this.`, fieldname, ` != that1.`, fieldname, `{`) p.In() p.P(`if this.`, fieldname, ` < that1.`, fieldname, `{`) p.In() p.P(`return -1`) p.Out() p.P(`}`) p.P(`return 1`) p.Out() p.P(`}`) } } } } else { p.P(`if len(this.`, fieldname, `) != len(that1.`, fieldname, `) {`) p.In() p.P(`if len(this.`, fieldname, `) < len(that1.`, fieldname, `) {`) p.In() p.P(`return -1`) p.Out() p.P(`}`) p.P(`return 1`) p.Out() p.P(`}`) p.P(`for i := range this.`, fieldname, ` {`) p.In() if ctype { p.P(`if c := this.`, fieldname, `[i].Compare(that1.`, fieldname, `[i]); c != 0 {`) p.In() p.P(`return c`) p.Out() p.P(`}`) } else { if p.IsMap(field) { m := p.GoMapType(nil, field) valuegoTyp, _ := p.GoType(nil, m.ValueField) valuegoAliasTyp, _ := p.GoType(nil, m.ValueAliasField) nullable, valuegoTyp, valuegoAliasTyp = generator.GoMapValueTypes(field, m.ValueField, valuegoTyp, valuegoAliasTyp) mapValue := m.ValueAliasField if mapValue.IsMessage() || p.IsGroup(mapValue) { if nullable && valuegoTyp == valuegoAliasTyp { p.P(`if c := this.`, fieldname, `[i].Compare(that1.`, fieldname, `[i]); c != 0 {`) } else { // Compare() has a pointer receiver, but map value is a value type a := `this.` + fieldname + `[i]` b := `that1.` + fieldname + `[i]` if valuegoTyp != valuegoAliasTyp { // cast back to the type that has the generated methods on it a = `(` + valuegoTyp + `)(` + a + `)` b = `(` + valuegoTyp + `)(` + b + `)` } p.P(`a := `, a) p.P(`b := `, b) if nullable { p.P(`if c := a.Compare(b); c != 0 {`) } else { p.P(`if c := (&a).Compare(&b); c != 0 {`) } } p.In() p.P(`return c`) p.Out() p.P(`}`) } else if mapValue.IsBytes() { p.P(`if c := `, p.bytesPkg.Use(), `.Compare(this.`, fieldname, `[i], that1.`, fieldname, `[i]); c != 0 {`) p.In() p.P(`return c`) p.Out() p.P(`}`) } else if mapValue.IsString() { p.P(`if this.`, fieldname, `[i] != that1.`, fieldname, `[i] {`) p.In() p.P(`if this.`, fieldname, `[i] < that1.`, fieldname, `[i] {`) p.In() p.P(`return -1`) p.Out() p.P(`}`) p.P(`return 1`) p.Out() p.P(`}`) } else { p.P(`if this.`, fieldname, `[i] != that1.`, fieldname, `[i] {`) p.In() p.P(`if this.`, fieldname, `[i] < that1.`, fieldname, `[i] {`) p.In() p.P(`return -1`) p.Out() p.P(`}`) p.P(`return 1`) p.Out() p.P(`}`) } } else if field.IsMessage() || p.IsGroup(field) { if nullable { p.P(`if c := this.`, fieldname, `[i].Compare(that1.`, fieldname, `[i]); c != 0 {`) p.In() p.P(`return c`) p.Out() p.P(`}`) } else { p.P(`if c := this.`, fieldname, `[i].Compare(&that1.`, fieldname, `[i]); c != 0 {`) p.In() p.P(`return c`) p.Out() p.P(`}`) } } else if field.IsBytes() { p.P(`if c := `, p.bytesPkg.Use(), `.Compare(this.`, fieldname, `[i], that1.`, fieldname, `[i]); c != 0 {`) p.In() p.P(`return c`) p.Out() p.P(`}`) } else if field.IsString() { p.P(`if this.`, fieldname, `[i] != that1.`, fieldname, `[i] {`) p.In() p.P(`if this.`, fieldname, `[i] < that1.`, fieldname, `[i] {`) p.In() p.P(`return -1`) p.Out() p.P(`}`) p.P(`return 1`) p.Out() p.P(`}`) } else if field.IsBool() { p.P(`if this.`, fieldname, `[i] != that1.`, fieldname, `[i] {`) p.In() p.P(`if !this.`, fieldname, `[i] {`) p.In() p.P(`return -1`) p.Out() p.P(`}`) p.P(`return 1`) p.Out() p.P(`}`) } else { p.P(`if this.`, fieldname, `[i] != that1.`, fieldname, `[i] {`) p.In() p.P(`if this.`, fieldname, `[i] < that1.`, fieldname, `[i] {`) p.In() p.P(`return -1`) p.Out() p.P(`}`) p.P(`return 1`) p.Out() p.P(`}`) } } p.Out() p.P(`}`) } } func (p *plugin) generateMessage(file *generator.FileDescriptor, message *generator.Descriptor) { ccTypeName := generator.CamelCaseSlice(message.TypeName()) p.P(`func (this *`, ccTypeName, `) Compare(that interface{}) int {`) p.In() p.generateMsgNullAndTypeCheck(ccTypeName) oneofs := make(map[string]struct{}) for _, field := range message.Field { oneof := field.OneofIndex != nil if oneof { fieldname := p.GetFieldName(message, field) if _, ok := oneofs[fieldname]; ok { continue } else { oneofs[fieldname] = struct{}{} } p.P(`if that1.`, fieldname, ` == nil {`) p.In() p.P(`if this.`, fieldname, ` != nil {`) p.In() p.P(`return 1`) p.Out() p.P(`}`) p.Out() p.P(`} else if this.`, fieldname, ` == nil {`) p.In() p.P(`return -1`) p.Out() p.P(`} else if c := this.`, fieldname, `.Compare(that1.`, fieldname, `); c != 0 {`) p.In() p.P(`return c`) p.Out() p.P(`}`) } else { p.generateField(file, message, field) } } if message.DescriptorProto.HasExtension() { fieldname := "XXX_extensions" if gogoproto.HasExtensionsMap(file.FileDescriptorProto, message.DescriptorProto) { p.P(`extkeys := make([]int32, 0, len(this.`, fieldname, `)+len(that1.`, fieldname, `))`) p.P(`for k, _ := range this.`, fieldname, ` {`) p.In() p.P(`extkeys = append(extkeys, k)`) p.Out() p.P(`}`) p.P(`for k, _ := range that1.`, fieldname, ` {`) p.In() p.P(`if _, ok := this.`, fieldname, `[k]; !ok {`) p.In() p.P(`extkeys = append(extkeys, k)`) p.Out() p.P(`}`) p.Out() p.P(`}`) p.P(p.sortkeysPkg.Use(), `.Int32s(extkeys)`) p.P(`for _, k := range extkeys {`) p.In() p.P(`if v, ok := this.`, fieldname, `[k]; ok {`) p.In() p.P(`if v2, ok := that1.`, fieldname, `[k]; ok {`) p.In() p.P(`if c := v.Compare(&v2); c != 0 {`) p.In() p.P(`return c`) p.Out() p.P(`}`) p.Out() p.P(`} else {`) p.In() p.P(`return 1`) p.Out() p.P(`}`) p.Out() p.P(`} else {`) p.In() p.P(`return -1`) p.Out() p.P(`}`) p.Out() p.P(`}`) } else { p.P(`if c := `, p.bytesPkg.Use(), `.Compare(this.`, fieldname, `, that1.`, fieldname, `); c != 0 {`) p.In() p.P(`return c`) p.Out() p.P(`}`) } } if gogoproto.HasUnrecognized(file.FileDescriptorProto, message.DescriptorProto) { fieldname := "XXX_unrecognized" p.P(`if c := `, p.bytesPkg.Use(), `.Compare(this.`, fieldname, `, that1.`, fieldname, `); c != 0 {`) p.In() p.P(`return c`) p.Out() p.P(`}`) } p.P(`return 0`) p.Out() p.P(`}`) //Generate Compare methods for oneof fields m := proto.Clone(message.DescriptorProto).(*descriptor.DescriptorProto) for _, field := range m.Field { oneof := field.OneofIndex != nil if !oneof { continue } ccTypeName := p.OneOfTypeName(message, field) p.P(`func (this *`, ccTypeName, `) Compare(that interface{}) int {`) p.In() p.generateMsgNullAndTypeCheck(ccTypeName) vanity.TurnOffNullableForNativeTypesWithoutDefaultsOnly(field) p.generateField(file, message, field) p.P(`return 0`) p.Out() p.P(`}`) } } func init() { generator.RegisterPlugin(NewPlugin()) }