1// Protocol Buffers for Go with Gadgets
2//
3// Copyright (c) 2013, The GoGo Authors. All rights reserved.
4// http://github.com/gogo/protobuf
5//
6// Redistribution and use in source and binary forms, with or without
7// modification, are permitted provided that the following conditions are
8// met:
9//
10//     * Redistributions of source code must retain the above copyright
11// notice, this list of conditions and the following disclaimer.
12//     * Redistributions in binary form must reproduce the above
13// copyright notice, this list of conditions and the following disclaimer
14// in the documentation and/or other materials provided with the
15// distribution.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29/*
30The onlyone plugin generates code for the onlyone extension.
31All fields must be nullable and only one of the fields may be set, like a union.
32Two methods are generated
33
34  GetValue() interface{}
35
36and
37
38  SetValue(v interface{}) (set bool)
39
40These provide easier interaction with a onlyone.
41
42The onlyone extension is not called union as this causes compile errors in the C++ generated code.
43There can only be one ;)
44
45It is enabled by the following extensions:
46
47  - onlyone
48  - onlyone_all
49
50The onlyone plugin also generates a test given it is enabled using one of the following extensions:
51
52  - testgen
53  - testgen_all
54
55Lets look at:
56
57  github.com/gogo/protobuf/test/example/example.proto
58
59Btw all the output can be seen at:
60
61  github.com/gogo/protobuf/test/example/*
62
63The following message:
64
65  message U {
66	  option (gogoproto.onlyone) = true;
67	  optional A A = 1;
68	  optional B B = 2;
69  }
70
71given to the onlyone plugin, will generate code which looks a lot like this:
72
73	func (this *U) GetValue() interface{} {
74		if this.A != nil {
75			return this.A
76		}
77		if this.B != nil {
78			return this.B
79		}
80		return nil
81	}
82
83	func (this *U) SetValue(value interface{}) bool {
84		switch vt := value.(type) {
85		case *A:
86			this.A = vt
87		case *B:
88			this.B = vt
89		default:
90			return false
91		}
92		return true
93	}
94
95and the following test code:
96
97  func TestUUnion(t *testing.T) {
98	popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano()))
99	p := NewPopulatedU(popr)
100	v := p.GetValue()
101	msg := &U{}
102	if !msg.SetValue(v) {
103		t.Fatalf("Union: Could not set Value")
104	}
105	if !p.Equal(msg) {
106		t.Fatalf("%#v !Union Equal %#v", msg, p)
107	}
108  }
109
110*/
111package union
112
113import (
114	"github.com/gogo/protobuf/gogoproto"
115	"github.com/gogo/protobuf/protoc-gen-gogo/generator"
116)
117
118type union struct {
119	*generator.Generator
120	generator.PluginImports
121}
122
123func NewUnion() *union {
124	return &union{}
125}
126
127func (p *union) Name() string {
128	return "union"
129}
130
131func (p *union) Init(g *generator.Generator) {
132	p.Generator = g
133}
134
135func (p *union) Generate(file *generator.FileDescriptor) {
136	p.PluginImports = generator.NewPluginImports(p.Generator)
137
138	for _, message := range file.Messages() {
139		if !gogoproto.IsUnion(file.FileDescriptorProto, message.DescriptorProto) {
140			continue
141		}
142		if message.DescriptorProto.HasExtension() {
143			panic("onlyone does not currently support extensions")
144		}
145		if message.DescriptorProto.GetOptions().GetMapEntry() {
146			continue
147		}
148
149		ccTypeName := generator.CamelCaseSlice(message.TypeName())
150		p.P(`func (this *`, ccTypeName, `) GetValue() interface{} {`)
151		p.In()
152		for _, field := range message.Field {
153			fieldname := p.GetFieldName(message, field)
154			if fieldname == "Value" {
155				panic("cannot have a onlyone message " + ccTypeName + " with a field named Value")
156			}
157			p.P(`if this.`, fieldname, ` != nil {`)
158			p.In()
159			p.P(`return this.`, fieldname)
160			p.Out()
161			p.P(`}`)
162		}
163		p.P(`return nil`)
164		p.Out()
165		p.P(`}`)
166		p.P(``)
167		p.P(`func (this *`, ccTypeName, `) SetValue(value interface{}) bool {`)
168		p.In()
169		p.P(`switch vt := value.(type) {`)
170		p.In()
171		for _, field := range message.Field {
172			fieldname := p.GetFieldName(message, field)
173			goTyp, _ := p.GoType(message, field)
174			p.P(`case `, goTyp, `:`)
175			p.In()
176			p.P(`this.`, fieldname, ` = vt`)
177			p.Out()
178		}
179		p.P(`default:`)
180		p.In()
181		for _, field := range message.Field {
182			fieldname := p.GetFieldName(message, field)
183			if field.IsMessage() {
184				goTyp, _ := p.GoType(message, field)
185				obj := p.ObjectNamed(field.GetTypeName()).(*generator.Descriptor)
186
187				if gogoproto.IsUnion(obj.File().FileDescriptorProto, obj.DescriptorProto) {
188					p.P(`this.`, fieldname, ` = new(`, generator.GoTypeToName(goTyp), `)`)
189					p.P(`if set := this.`, fieldname, `.SetValue(value); set {`)
190					p.In()
191					p.P(`return true`)
192					p.Out()
193					p.P(`}`)
194					p.P(`this.`, fieldname, ` = nil`)
195				}
196			}
197		}
198		p.P(`return false`)
199		p.Out()
200		p.P(`}`)
201		p.P(`return true`)
202		p.Out()
203		p.P(`}`)
204	}
205}
206
207func init() {
208	generator.RegisterPlugin(NewUnion())
209}
210