diff --git a/Blueprints b/Blueprints index d58169e..c3c8975 100644 --- a/Blueprints +++ b/Blueprints @@ -79,6 +79,7 @@ bootstrap_go_package { "proptools/clone.go", "proptools/escape.go", "proptools/extend.go", + "proptools/filter.go", "proptools/proptools.go", "proptools/tag.go", "proptools/typeequal.go", @@ -87,6 +88,7 @@ bootstrap_go_package { "proptools/clone_test.go", "proptools/escape_test.go", "proptools/extend_test.go", + "proptools/filter_test.go", "proptools/tag_test.go", "proptools/typeequal_test.go", ], diff --git a/go.mod b/go.mod index 933cd12..fe96d45 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,3 @@ module github.com/google/blueprint + +go 1.13 diff --git a/module_ctx.go b/module_ctx.go index bc05787..be5d974 100644 --- a/module_ctx.go +++ b/module_ctx.go @@ -712,7 +712,7 @@ type TopDownMutatorContext interface { // CreateModule creates a new module by calling the factory method for the specified moduleType, and applies // the specified property structs to it as if the properties were set in a blueprint file. - CreateModule(ModuleFactory, ...interface{}) + CreateModule(ModuleFactory, ...interface{}) Module } type BottomUpMutatorContext interface { @@ -934,7 +934,7 @@ func (mctx *mutatorContext) Rename(name string) { mctx.rename = append(mctx.rename, rename{mctx.module.group, name}) } -func (mctx *mutatorContext) CreateModule(factory ModuleFactory, props ...interface{}) { +func (mctx *mutatorContext) CreateModule(factory ModuleFactory, props ...interface{}) Module { module := mctx.context.newModule(factory) module.relBlueprintsFile = mctx.module.relBlueprintsFile @@ -950,6 +950,8 @@ func (mctx *mutatorContext) CreateModule(factory ModuleFactory, props ...interfa } mctx.newModules = append(mctx.newModules, module) + + return module.logicModule } // SimpleName is an embeddable object to implement the ModuleContext.Name method using a property diff --git a/proptools/filter.go b/proptools/filter.go new file mode 100644 index 0000000..7a61b02 --- /dev/null +++ b/proptools/filter.go @@ -0,0 +1,159 @@ +// Copyright 2019 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 proptools + +import ( + "reflect" +) + +type FilterFieldPredicate func(field reflect.StructField, string string) (bool, reflect.StructField) + +func filterPropertyStructFields(fields []reflect.StructField, prefix string, predicate FilterFieldPredicate) (filteredFields []reflect.StructField, filtered bool) { + for _, field := range fields { + var keep bool + if keep, field = predicate(field, prefix); !keep { + filtered = true + continue + } + + subPrefix := field.Name + if prefix != "" { + subPrefix = prefix + "." + subPrefix + } + + // Recurse into structs + switch field.Type.Kind() { + case reflect.Struct: + var subFiltered bool + field.Type, subFiltered = filterPropertyStruct(field.Type, subPrefix, predicate) + filtered = filtered || subFiltered + if field.Type == nil { + continue + } + case reflect.Ptr: + if field.Type.Elem().Kind() == reflect.Struct { + nestedType, subFiltered := filterPropertyStruct(field.Type.Elem(), subPrefix, predicate) + filtered = filtered || subFiltered + if nestedType == nil { + continue + } + field.Type = reflect.PtrTo(nestedType) + } + case reflect.Interface: + panic("Interfaces are not supported in filtered property structs") + } + + filteredFields = append(filteredFields, field) + } + + return filteredFields, filtered +} + +// FilterPropertyStruct takes a reflect.Type that is either a struct or a pointer to a struct, and returns a +// reflect.Type that only contains the fields in the original type for which predicate returns true, and a bool +// that is true if the new struct type has fewer fields than the original type. If there are no fields in the +// original type for which predicate returns true it returns nil and true. +func FilterPropertyStruct(prop reflect.Type, predicate FilterFieldPredicate) (filteredProp reflect.Type, filtered bool) { + return filterPropertyStruct(prop, "", predicate) +} + +func filterPropertyStruct(prop reflect.Type, prefix string, predicate FilterFieldPredicate) (filteredProp reflect.Type, filtered bool) { + var fields []reflect.StructField + + ptr := prop.Kind() == reflect.Ptr + if ptr { + prop = prop.Elem() + } + + for i := 0; i < prop.NumField(); i++ { + fields = append(fields, prop.Field(i)) + } + + filteredFields, filtered := filterPropertyStructFields(fields, prefix, predicate) + + if len(filteredFields) == 0 { + return nil, true + } + + if !filtered { + if ptr { + return reflect.PtrTo(prop), false + } + return prop, false + } + + ret := reflect.StructOf(filteredFields) + if ptr { + ret = reflect.PtrTo(ret) + } + + return ret, true +} + +// FilterPropertyStructSharded takes a reflect.Type that is either a sturct or a pointer to a struct, and returns a list +// of reflect.Type that only contains the fields in the original type for which predicate returns true, and a bool that +// is true if the new struct type has fewer fields than the original type. If there are no fields in the original type +// for which predicate returns true it returns nil and true. Each returned struct type will have a maximum of 10 top +// level fields in it to attempt to avoid hitting the 65535 byte type name length limit in reflect.StructOf +// (reflect.nameFrom: name too long), although the limit can still be reached with a single struct field with many +// fields in it. +func FilterPropertyStructSharded(prop reflect.Type, predicate FilterFieldPredicate) (filteredProp []reflect.Type, filtered bool) { + var fields []reflect.StructField + + ptr := prop.Kind() == reflect.Ptr + if ptr { + prop = prop.Elem() + } + + for i := 0; i < prop.NumField(); i++ { + fields = append(fields, prop.Field(i)) + } + + fields, filtered = filterPropertyStructFields(fields, "", predicate) + if !filtered { + if ptr { + return []reflect.Type{reflect.PtrTo(prop)}, false + } + return []reflect.Type{prop}, false + } + + if len(fields) == 0 { + return nil, true + } + + shards := shardFields(fields, 10) + + for _, shard := range shards { + s := reflect.StructOf(shard) + if ptr { + s = reflect.PtrTo(s) + } + filteredProp = append(filteredProp, s) + } + + return filteredProp, true +} + +func shardFields(fields []reflect.StructField, shardSize int) [][]reflect.StructField { + ret := make([][]reflect.StructField, 0, (len(fields)+shardSize-1)/shardSize) + for len(fields) > shardSize { + ret = append(ret, fields[0:shardSize]) + fields = fields[shardSize:] + } + if len(fields) > 0 { + ret = append(ret, fields) + } + return ret +} diff --git a/proptools/filter_test.go b/proptools/filter_test.go new file mode 100644 index 0000000..695549a --- /dev/null +++ b/proptools/filter_test.go @@ -0,0 +1,239 @@ +// Copyright 2019 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 proptools + +import ( + "reflect" + "testing" +) + +type Named struct { + A *string `keep:"true"` + B *string +} + +type NamedAllFiltered struct { + A *string +} + +type NamedNoneFiltered struct { + A *string `keep:"true"` +} + +func TestFilterPropertyStruct(t *testing.T) { + tests := []struct { + name string + in interface{} + out interface{} + filtered bool + }{ + // Property tests + { + name: "basic", + in: &struct { + A *string `keep:"true"` + B *string + }{}, + out: &struct { + A *string + }{}, + filtered: true, + }, + { + name: "all filtered", + in: &struct { + A *string + }{}, + out: nil, + filtered: true, + }, + { + name: "none filtered", + in: &struct { + A *string `keep:"true"` + }{}, + out: &struct { + A *string `keep:"true"` + }{}, + filtered: false, + }, + + // Sub-struct tests + { + name: "substruct", + in: &struct { + A struct { + A *string `keep:"true"` + B *string + } `keep:"true"` + }{}, + out: &struct { + A struct { + A *string + } + }{}, + filtered: true, + }, + { + name: "substruct all filtered", + in: &struct { + A struct { + A *string + } `keep:"true"` + }{}, + out: nil, + filtered: true, + }, + { + name: "substruct none filtered", + in: &struct { + A struct { + A *string `keep:"true"` + } `keep:"true"` + }{}, + out: &struct { + A struct { + A *string `keep:"true"` + } `keep:"true"` + }{}, + filtered: false, + }, + + // Named sub-struct tests + { + name: "named substruct", + in: &struct { + A Named `keep:"true"` + }{}, + out: &struct { + A struct { + A *string + } + }{}, + filtered: true, + }, + { + name: "substruct all filtered", + in: &struct { + A NamedAllFiltered `keep:"true"` + }{}, + out: nil, + filtered: true, + }, + { + name: "substruct none filtered", + in: &struct { + A NamedNoneFiltered `keep:"true"` + }{}, + out: &struct { + A NamedNoneFiltered `keep:"true"` + }{}, + filtered: false, + }, + + // Pointer to sub-struct tests + { + name: "pointer substruct", + in: &struct { + A *struct { + A *string `keep:"true"` + B *string + } `keep:"true"` + }{}, + out: &struct { + A *struct { + A *string + } + }{}, + filtered: true, + }, + { + name: "pointer substruct all filtered", + in: &struct { + A *struct { + A *string + } `keep:"true"` + }{}, + out: nil, + filtered: true, + }, + { + name: "pointer substruct none filtered", + in: &struct { + A *struct { + A *string `keep:"true"` + } `keep:"true"` + }{}, + out: &struct { + A *struct { + A *string `keep:"true"` + } `keep:"true"` + }{}, + filtered: false, + }, + + // Pointer to named sub-struct tests + { + name: "pointer named substruct", + in: &struct { + A *Named `keep:"true"` + }{}, + out: &struct { + A *struct { + A *string + } + }{}, + filtered: true, + }, + { + name: "pointer substruct all filtered", + in: &struct { + A *NamedAllFiltered `keep:"true"` + }{}, + out: nil, + filtered: true, + }, + { + name: "pointer substruct none filtered", + in: &struct { + A *NamedNoneFiltered `keep:"true"` + }{}, + out: &struct { + A *NamedNoneFiltered `keep:"true"` + }{}, + filtered: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + out, filtered := FilterPropertyStruct(reflect.TypeOf(test.in), + func(field reflect.StructField, prefix string) (bool, reflect.StructField) { + if HasTag(field, "keep", "true") { + field.Tag = "" + return true, field + } + return false, field + }) + if filtered != test.filtered { + t.Errorf("expected filtered %v, got %v", test.filtered, filtered) + } + expected := reflect.TypeOf(test.out) + if out != expected { + t.Errorf("expected type %v, got %v", expected, out) + } + }) + } +} diff --git a/proptools/proptools.go b/proptools/proptools.go index e6e3ae7..6881828 100644 --- a/proptools/proptools.go +++ b/proptools/proptools.go @@ -82,3 +82,18 @@ func StringDefault(s *string, def string) string { func String(s *string) string { return StringDefault(s, "") } + +// IntDefault takes a pointer to an int64 and returns the value pointed to by the pointer cast to int +// if it is non-nil, or def if the pointer is nil. +func IntDefault(i *int64, def int) int { + if i != nil { + return int(*i) + } + return def +} + +// Int takes a pointer to an int64 and returns the value pointed to by the pointer cast to int +// if it is non-nil, or 0 if the pointer is nil. +func Int(i *int64) int { + return IntDefault(i, 0) +}