blob: 7a61b027ee6be483b9c56da698002b407885c59a [file] [log] [blame]
// 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
}