blob: f513a30779ddacaa14b0c79928a5edaf7c1a99bc [file] [log] [blame]
Colin Cross8e0c5112015-01-23 14:15:10 -08001// Copyright 2014 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
Jamie Gennis1bc967e2014-05-27 16:34:41 -070015package blueprint
16
17import (
18 "blueprint/parser"
Jamie Gennis87622922014-09-30 11:38:25 -070019 "blueprint/proptools"
Jamie Gennis1bc967e2014-05-27 16:34:41 -070020 "fmt"
21 "reflect"
Colin Cross11a114f2014-12-17 16:46:09 -080022 "strings"
Jamie Gennis1bc967e2014-05-27 16:34:41 -070023)
24
25type packedProperty struct {
26 property *parser.Property
27 unpacked bool
28}
29
30func unpackProperties(propertyDefs []*parser.Property,
Jamie Gennis87622922014-09-30 11:38:25 -070031 propertiesStructs ...interface{}) (map[string]*parser.Property, []error) {
Jamie Gennis1bc967e2014-05-27 16:34:41 -070032
33 propertyMap := make(map[string]*packedProperty)
Jamie Gennis87622922014-09-30 11:38:25 -070034 errs := buildPropertyMap("", propertyDefs, propertyMap)
35 if len(errs) > 0 {
36 return nil, errs
37 }
38
39 for _, properties := range propertiesStructs {
40 propertiesValue := reflect.ValueOf(properties)
41 if propertiesValue.Kind() != reflect.Ptr {
42 panic("properties must be a pointer to a struct")
43 }
44
45 propertiesValue = propertiesValue.Elem()
46 if propertiesValue.Kind() != reflect.Struct {
47 panic("properties must be a pointer to a struct")
48 }
49
50 newErrs := unpackStructValue("", propertiesValue, propertyMap)
51 errs = append(errs, newErrs...)
52
53 if len(errs) >= maxErrors {
54 return nil, errs
55 }
56 }
57
58 // Report any properties that didn't have corresponding struct fields as
59 // errors.
60 result := make(map[string]*parser.Property)
61 for name, packedProperty := range propertyMap {
62 result[name] = packedProperty.property
63 if !packedProperty.unpacked {
64 err := &Error{
65 Err: fmt.Errorf("unrecognized property %q", name),
66 Pos: packedProperty.property.Pos,
67 }
68 errs = append(errs, err)
69 }
70 }
71
72 if len(errs) > 0 {
73 return nil, errs
74 }
75
76 return result, nil
77}
78
79func buildPropertyMap(namePrefix string, propertyDefs []*parser.Property,
80 propertyMap map[string]*packedProperty) (errs []error) {
81
Jamie Gennis1bc967e2014-05-27 16:34:41 -070082 for _, propertyDef := range propertyDefs {
Colin Crossd1facc12015-01-08 14:56:03 -080083 name := namePrefix + propertyDef.Name.Name
Jamie Gennis1bc967e2014-05-27 16:34:41 -070084 if first, present := propertyMap[name]; present {
Jamie Gennis87622922014-09-30 11:38:25 -070085 if first.property == propertyDef {
86 // We've already added this property.
87 continue
88 }
89
Jamie Gennis1bc967e2014-05-27 16:34:41 -070090 errs = append(errs, &Error{
91 Err: fmt.Errorf("property %q already defined", name),
92 Pos: propertyDef.Pos,
93 })
94 errs = append(errs, &Error{
Jamie Gennisd4c53d82014-06-22 17:02:55 -070095 Err: fmt.Errorf("<-- previous definition here"),
Jamie Gennis1bc967e2014-05-27 16:34:41 -070096 Pos: first.property.Pos,
97 })
98 if len(errs) >= maxErrors {
99 return errs
100 }
101 continue
102 }
103
104 propertyMap[name] = &packedProperty{
105 property: propertyDef,
106 unpacked: false,
107 }
Jamie Gennis87622922014-09-30 11:38:25 -0700108
109 // We intentionally do not rescursively add MapValue properties to the
110 // property map here. Instead we add them when we encounter a struct
111 // into which they can be unpacked. We do this so that if we never
112 // encounter such a struct then the "unrecognized property" error will
113 // be reported only once for the map property and not for each of its
114 // sub-properties.
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700115 }
116
Jamie Gennis87622922014-09-30 11:38:25 -0700117 return
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700118}
119
Jamie Gennis87622922014-09-30 11:38:25 -0700120func unpackStructValue(namePrefix string, structValue reflect.Value,
Jamie Gennis1174c692014-10-05 07:41:44 -0700121 propertyMap map[string]*packedProperty) []error {
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700122
123 structType := structValue.Type()
124
125 var errs []error
126 for i := 0; i < structValue.NumField(); i++ {
127 fieldValue := structValue.Field(i)
128 field := structType.Field(i)
129
Jamie Gennisd4c53d82014-06-22 17:02:55 -0700130 if field.PkgPath != "" {
131 // This is an unexported field, so just skip it.
132 continue
133 }
134
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700135 if !fieldValue.CanSet() {
136 panic(fmt.Errorf("field %s is not settable", field.Name))
137 }
138
139 // To make testing easier we validate the struct field's type regardless
140 // of whether or not the property was specified in the parsed string.
141 switch kind := fieldValue.Kind(); kind {
Jamie Gennis87622922014-09-30 11:38:25 -0700142 case reflect.Bool, reflect.String, reflect.Struct:
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700143 // Do nothing
144 case reflect.Slice:
145 elemType := field.Type.Elem()
146 if elemType.Kind() != reflect.String {
147 panic(fmt.Errorf("field %s is a non-string slice", field.Name))
148 }
Jamie Gennis87622922014-09-30 11:38:25 -0700149 case reflect.Interface:
150 if fieldValue.IsNil() {
151 panic(fmt.Errorf("field %s contains a nil interface",
152 field.Name))
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700153 }
Jamie Gennis87622922014-09-30 11:38:25 -0700154 fieldValue = fieldValue.Elem()
155 elemType := fieldValue.Type()
156 if elemType.Kind() != reflect.Ptr {
157 panic(fmt.Errorf("field %s contains a non-pointer interface",
158 field.Name))
159 }
160 fallthrough
161 case reflect.Ptr:
162 if fieldValue.IsNil() {
163 panic(fmt.Errorf("field %s contains a nil pointer",
164 field.Name))
165 }
166 fieldValue = fieldValue.Elem()
167 elemType := fieldValue.Type()
168 if elemType.Kind() != reflect.Struct {
169 panic(fmt.Errorf("field %s contains a non-struct pointer",
170 field.Name))
171 }
Colin Cross11a114f2014-12-17 16:46:09 -0800172 case reflect.Int, reflect.Uint:
173 if !hasTag(field, "blueprint", "mutated") {
174 panic(fmt.Errorf(`int field %s must be tagged blueprint:"mutated"`, field.Name))
175 }
176
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700177 default:
178 panic(fmt.Errorf("unsupported kind for field %s: %s",
179 field.Name, kind))
180 }
181
182 // Get the property value if it was specified.
Jamie Gennis87622922014-09-30 11:38:25 -0700183 propertyName := namePrefix + proptools.PropertyNameForField(field.Name)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700184 packedProperty, ok := propertyMap[propertyName]
185 if !ok {
186 // This property wasn't specified.
187 continue
188 }
189
Colin Cross11a114f2014-12-17 16:46:09 -0800190 var newErrs []error
191
192 if hasTag(field, "blueprint", "mutated") {
193 errs = append(errs,
194 fmt.Errorf("mutated field %s cannot be set in a Blueprint file", propertyName))
195 if len(errs) >= maxErrors {
196 return errs
197 }
198 continue
199 }
200
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700201 packedProperty.unpacked = true
202
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700203 switch kind := fieldValue.Kind(); kind {
204 case reflect.Bool:
Jamie Gennis1174c692014-10-05 07:41:44 -0700205 newErrs = unpackBool(fieldValue, packedProperty.property)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700206 case reflect.String:
Jamie Gennis1174c692014-10-05 07:41:44 -0700207 newErrs = unpackString(fieldValue, packedProperty.property)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700208 case reflect.Slice:
Jamie Gennis1174c692014-10-05 07:41:44 -0700209 newErrs = unpackSlice(fieldValue, packedProperty.property)
Jamie Gennis87622922014-09-30 11:38:25 -0700210 case reflect.Struct:
211 newErrs = unpackStruct(propertyName+".", fieldValue,
212 packedProperty.property, propertyMap)
213 case reflect.Ptr, reflect.Interface:
214 structValue := fieldValue.Elem()
215 newErrs = unpackStruct(propertyName+".", structValue,
216 packedProperty.property, propertyMap)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700217 }
218 errs = append(errs, newErrs...)
219 if len(errs) >= maxErrors {
220 return errs
221 }
222 }
223
224 return errs
225}
226
Jamie Gennis1174c692014-10-05 07:41:44 -0700227func unpackBool(boolValue reflect.Value, property *parser.Property) []error {
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700228 if property.Value.Type != parser.Bool {
229 return []error{
230 fmt.Errorf("%s: can't assign %s value to %s property %q",
231 property.Value.Pos, property.Value.Type, parser.Bool,
232 property.Name),
233 }
234 }
Jamie Gennis1174c692014-10-05 07:41:44 -0700235 boolValue.SetBool(property.Value.BoolValue)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700236 return nil
237}
238
239func unpackString(stringValue reflect.Value,
Jamie Gennis1174c692014-10-05 07:41:44 -0700240 property *parser.Property) []error {
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700241
242 if property.Value.Type != parser.String {
243 return []error{
244 fmt.Errorf("%s: can't assign %s value to %s property %q",
245 property.Value.Pos, property.Value.Type, parser.String,
246 property.Name),
247 }
248 }
Jamie Gennis1174c692014-10-05 07:41:44 -0700249 stringValue.SetString(property.Value.StringValue)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700250 return nil
251}
252
Jamie Gennis1174c692014-10-05 07:41:44 -0700253func unpackSlice(sliceValue reflect.Value, property *parser.Property) []error {
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700254 if property.Value.Type != parser.List {
255 return []error{
256 fmt.Errorf("%s: can't assign %s value to %s property %q",
257 property.Value.Pos, property.Value.Type, parser.List,
258 property.Name),
259 }
260 }
261
262 var list []string
263 for _, value := range property.Value.ListValue {
264 if value.Type != parser.String {
265 // The parser should not produce this.
266 panic("non-string value found in list")
267 }
268 list = append(list, value.StringValue)
269 }
270
Jamie Gennis1174c692014-10-05 07:41:44 -0700271 sliceValue.Set(reflect.ValueOf(list))
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700272 return nil
273}
274
Jamie Gennis87622922014-09-30 11:38:25 -0700275func unpackStruct(namePrefix string, structValue reflect.Value,
276 property *parser.Property,
277 propertyMap map[string]*packedProperty) []error {
278
279 if property.Value.Type != parser.Map {
280 return []error{
281 fmt.Errorf("%s: can't assign %s value to %s property %q",
282 property.Value.Pos, property.Value.Type, parser.Map,
283 property.Name),
284 }
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700285 }
Jamie Gennis87622922014-09-30 11:38:25 -0700286
287 errs := buildPropertyMap(namePrefix, property.Value.MapValue, propertyMap)
288 if len(errs) > 0 {
289 return errs
290 }
291
292 return unpackStructValue(namePrefix, structValue, propertyMap)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700293}
Colin Cross11a114f2014-12-17 16:46:09 -0800294
295func hasTag(field reflect.StructField, name, value string) bool {
296 tag := field.Tag.Get(name)
297 for _, entry := range strings.Split(tag, ",") {
298 if entry == value {
299 return true
300 }
301 }
302
303 return false
304}