Golang程序  |  399行  |  11.17 KB

// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package blueprint

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"

	"github.com/google/blueprint/parser"
	"github.com/google/blueprint/proptools"
)

type packedProperty struct {
	property *parser.Property
	unpacked bool
}

func unpackProperties(propertyDefs []*parser.Property,
	propertiesStructs ...interface{}) (map[string]*parser.Property, []error) {

	propertyMap := make(map[string]*packedProperty)
	errs := buildPropertyMap("", propertyDefs, propertyMap)
	if len(errs) > 0 {
		return nil, errs
	}

	for _, properties := range propertiesStructs {
		propertiesValue := reflect.ValueOf(properties)
		if propertiesValue.Kind() != reflect.Ptr {
			panic("properties must be a pointer to a struct")
		}

		propertiesValue = propertiesValue.Elem()
		if propertiesValue.Kind() != reflect.Struct {
			panic("properties must be a pointer to a struct")
		}

		newErrs := unpackStructValue("", propertiesValue, propertyMap, "", "")
		errs = append(errs, newErrs...)

		if len(errs) >= maxErrors {
			return nil, errs
		}
	}

	// Report any properties that didn't have corresponding struct fields as
	// errors.
	result := make(map[string]*parser.Property)
	for name, packedProperty := range propertyMap {
		result[name] = packedProperty.property
		if !packedProperty.unpacked {
			err := &BlueprintError{
				Err: fmt.Errorf("unrecognized property %q", name),
				Pos: packedProperty.property.ColonPos,
			}
			errs = append(errs, err)
		}
	}

	if len(errs) > 0 {
		return nil, errs
	}

	return result, nil
}

func buildPropertyMap(namePrefix string, propertyDefs []*parser.Property,
	propertyMap map[string]*packedProperty) (errs []error) {

	for _, propertyDef := range propertyDefs {
		name := namePrefix + propertyDef.Name
		if first, present := propertyMap[name]; present {
			if first.property == propertyDef {
				// We've already added this property.
				continue
			}
			errs = append(errs, &BlueprintError{
				Err: fmt.Errorf("property %q already defined", name),
				Pos: propertyDef.ColonPos,
			})
			errs = append(errs, &BlueprintError{
				Err: fmt.Errorf("<-- previous definition here"),
				Pos: first.property.ColonPos,
			})
			if len(errs) >= maxErrors {
				return errs
			}
			continue
		}

		propertyMap[name] = &packedProperty{
			property: propertyDef,
			unpacked: false,
		}

		// We intentionally do not rescursively add MapValue properties to the
		// property map here.  Instead we add them when we encounter a struct
		// into which they can be unpacked.  We do this so that if we never
		// encounter such a struct then the "unrecognized property" error will
		// be reported only once for the map property and not for each of its
		// sub-properties.
	}

	return
}

func unpackStructValue(namePrefix string, structValue reflect.Value,
	propertyMap map[string]*packedProperty, filterKey, filterValue string) []error {

	structType := structValue.Type()

	var errs []error
	for i := 0; i < structValue.NumField(); i++ {
		fieldValue := structValue.Field(i)
		field := structType.Field(i)

		// In Go 1.7, runtime-created structs are unexported, so it's not
		// possible to create an exported anonymous field with a generated
		// type. So workaround this by special-casing "BlueprintEmbed" to
		// behave like an anonymous field for structure unpacking.
		if field.Name == "BlueprintEmbed" {
			field.Name = ""
			field.Anonymous = true
		}

		if field.PkgPath != "" {
			// This is an unexported field, so just skip it.
			continue
		}

		propertyName := namePrefix + proptools.PropertyNameForField(field.Name)

		if !fieldValue.CanSet() {
			panic(fmt.Errorf("field %s is not settable", propertyName))
		}

		// Get the property value if it was specified.
		packedProperty, propertyIsSet := propertyMap[propertyName]

		origFieldValue := fieldValue

		// To make testing easier we validate the struct field's type regardless
		// of whether or not the property was specified in the parsed string.
		// TODO(ccross): we don't validate types inside nil struct pointers
		// Move type validation to a function that runs on each factory once
		switch kind := fieldValue.Kind(); kind {
		case reflect.Bool, reflect.String, reflect.Struct:
			// Do nothing
		case reflect.Slice:
			elemType := field.Type.Elem()
			if elemType.Kind() != reflect.String {
				panic(fmt.Errorf("field %s is a non-string slice", propertyName))
			}
		case reflect.Interface:
			if fieldValue.IsNil() {
				panic(fmt.Errorf("field %s contains a nil interface", propertyName))
			}
			fieldValue = fieldValue.Elem()
			elemType := fieldValue.Type()
			if elemType.Kind() != reflect.Ptr {
				panic(fmt.Errorf("field %s contains a non-pointer interface", propertyName))
			}
			fallthrough
		case reflect.Ptr:
			switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind {
			case reflect.Struct:
				if fieldValue.IsNil() && (propertyIsSet || field.Anonymous) {
					// Instantiate nil struct pointers
					// Set into origFieldValue in case it was an interface, in which case
					// fieldValue points to the unsettable pointer inside the interface
					fieldValue = reflect.New(fieldValue.Type().Elem())
					origFieldValue.Set(fieldValue)
				}
				fieldValue = fieldValue.Elem()
			case reflect.Bool, reflect.Int64, reflect.String:
				// Nothing
			default:
				panic(fmt.Errorf("field %s contains a pointer to %s", propertyName, ptrKind))
			}

		case reflect.Int, reflect.Uint:
			if !proptools.HasTag(field, "blueprint", "mutated") {
				panic(fmt.Errorf(`int field %s must be tagged blueprint:"mutated"`, propertyName))
			}

		default:
			panic(fmt.Errorf("unsupported kind for field %s: %s", propertyName, kind))
		}

		if field.Anonymous && fieldValue.Kind() == reflect.Struct {
			newErrs := unpackStructValue(namePrefix, fieldValue, propertyMap, filterKey, filterValue)
			errs = append(errs, newErrs...)
			continue
		}

		if !propertyIsSet {
			// This property wasn't specified.
			continue
		}

		packedProperty.unpacked = true

		if proptools.HasTag(field, "blueprint", "mutated") {
			errs = append(errs,
				&BlueprintError{
					Err: fmt.Errorf("mutated field %s cannot be set in a Blueprint file", propertyName),
					Pos: packedProperty.property.ColonPos,
				})
			if len(errs) >= maxErrors {
				return errs
			}
			continue
		}

		if filterKey != "" && !proptools.HasTag(field, filterKey, filterValue) {
			errs = append(errs,
				&BlueprintError{
					Err: fmt.Errorf("filtered field %s cannot be set in a Blueprint file", propertyName),
					Pos: packedProperty.property.ColonPos,
				})
			if len(errs) >= maxErrors {
				return errs
			}
			continue
		}

		var newErrs []error

		if fieldValue.Kind() == reflect.Struct {
			localFilterKey, localFilterValue := filterKey, filterValue
			if k, v, err := HasFilter(field.Tag); err != nil {
				errs = append(errs, err)
				if len(errs) >= maxErrors {
					return errs
				}
			} else if k != "" {
				if filterKey != "" {
					errs = append(errs, fmt.Errorf("nested filter tag not supported on field %q",
						field.Name))
					if len(errs) >= maxErrors {
						return errs
					}
				} else {
					localFilterKey, localFilterValue = k, v
				}
			}
			newErrs = unpackStruct(propertyName+".", fieldValue,
				packedProperty.property, propertyMap, localFilterKey, localFilterValue)

			errs = append(errs, newErrs...)
			if len(errs) >= maxErrors {
				return errs
			}

			continue
		}

		// Handle basic types and pointers to basic types

		propertyValue, err := propertyToValue(fieldValue.Type(), packedProperty.property)
		if err != nil {
			errs = append(errs, err)
			if len(errs) >= maxErrors {
				return errs
			}
		}

		proptools.ExtendBasicType(fieldValue, propertyValue, proptools.Append)
	}

	return errs
}

func propertyToValue(typ reflect.Type, property *parser.Property) (reflect.Value, error) {
	var value reflect.Value

	var ptr bool
	if typ.Kind() == reflect.Ptr {
		typ = typ.Elem()
		ptr = true
	}

	switch kind := typ.Kind(); kind {
	case reflect.Bool:
		b, ok := property.Value.Eval().(*parser.Bool)
		if !ok {
			return value, fmt.Errorf("%s: can't assign %s value to bool property %q",
				property.Value.Pos(), property.Value.Type(), property.Name)
		}
		value = reflect.ValueOf(b.Value)

	case reflect.Int64:
		b, ok := property.Value.Eval().(*parser.Int64)
		if !ok {
			return value, fmt.Errorf("%s: can't assign %s value to int64 property %q",
				property.Value.Pos(), property.Value.Type(), property.Name)
		}
		value = reflect.ValueOf(b.Value)

	case reflect.String:
		s, ok := property.Value.Eval().(*parser.String)
		if !ok {
			return value, fmt.Errorf("%s: can't assign %s value to string property %q",
				property.Value.Pos(), property.Value.Type(), property.Name)
		}
		value = reflect.ValueOf(s.Value)

	case reflect.Slice:
		l, ok := property.Value.Eval().(*parser.List)
		if !ok {
			return value, fmt.Errorf("%s: can't assign %s value to list property %q",
				property.Value.Pos(), property.Value.Type(), property.Name)
		}

		list := make([]string, len(l.Values))
		for i, value := range l.Values {
			s, ok := value.Eval().(*parser.String)
			if !ok {
				// The parser should not produce this.
				panic(fmt.Errorf("non-string value %q found in list", value))
			}
			list[i] = s.Value
		}

		value = reflect.ValueOf(list)

	default:
		panic(fmt.Errorf("unexpected kind %s", kind))
	}

	if ptr {
		ptrValue := reflect.New(value.Type())
		ptrValue.Elem().Set(value)
		value = ptrValue
	}

	return value, nil
}

func unpackStruct(namePrefix string, structValue reflect.Value,
	property *parser.Property, propertyMap map[string]*packedProperty,
	filterKey, filterValue string) []error {

	m, ok := property.Value.Eval().(*parser.Map)
	if !ok {
		return []error{
			fmt.Errorf("%s: can't assign %s value to map property %q",
				property.Value.Pos(), property.Value.Type(), property.Name),
		}
	}

	errs := buildPropertyMap(namePrefix, m.Properties, propertyMap)
	if len(errs) > 0 {
		return errs
	}

	return unpackStructValue(namePrefix, structValue, propertyMap, filterKey, filterValue)
}

func HasFilter(field reflect.StructTag) (k, v string, err error) {
	tag := field.Get("blueprint")
	for _, entry := range strings.Split(tag, ",") {
		if strings.HasPrefix(entry, "filter") {
			if !strings.HasPrefix(entry, "filter(") || !strings.HasSuffix(entry, ")") {
				return "", "", fmt.Errorf("unexpected format for filter %q: missing ()", entry)
			}
			entry = strings.TrimPrefix(entry, "filter(")
			entry = strings.TrimSuffix(entry, ")")

			s := strings.Split(entry, ":")
			if len(s) != 2 {
				return "", "", fmt.Errorf("unexpected format for filter %q: expected single ':'", entry)
			}
			k = s[0]
			v, err = strconv.Unquote(s[1])
			if err != nil {
				return "", "", fmt.Errorf("unexpected format for filter %q: %s", entry, err.Error())
			}
			return k, v, nil
		}
	}

	return "", "", nil
}