blob: e6b333677e4261cc890e90771efca59f55cbc09e [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 (
"fmt"
"reflect"
"strconv"
)
type FilterFieldPredicate func(field reflect.StructField, string string) (bool, reflect.StructField)
type cantFitPanic struct {
field reflect.StructField
size int
}
func (x cantFitPanic) Error() string {
return fmt.Sprintf("Can't fit field %s %s %s size %d into %d",
x.field.Name, x.field.Type.String(), strconv.Quote(string(x.field.Tag)),
fieldToTypeNameSize(x.field, true)+2, x.size)
}
// All runtime created structs will have a name that starts with "struct {" and ends with "}"
const emptyStructTypeNameSize = len("struct {}")
func filterPropertyStructFields(fields []reflect.StructField, prefix string, maxTypeNameSize int,
predicate FilterFieldPredicate) (filteredFieldsShards [][]reflect.StructField, filtered bool) {
structNameSize := emptyStructTypeNameSize
var filteredFields []reflect.StructField
appendAndShardIfNameFull := func(field reflect.StructField) {
fieldTypeNameSize := fieldToTypeNameSize(field, true)
// Every field will have a space before it and either a semicolon or space after it.
fieldTypeNameSize += 2
if maxTypeNameSize > 0 && structNameSize+fieldTypeNameSize > maxTypeNameSize {
if len(filteredFields) == 0 {
if isStruct(field.Type) || isStructPtr(field.Type) {
// An error fitting the nested struct should have been caught when recursing
// into the nested struct.
panic(fmt.Errorf("Shouldn't happen: can't fit nested struct %q (%d) into %d",
field.Type.String(), len(field.Type.String()), maxTypeNameSize-structNameSize))
}
panic(cantFitPanic{field, maxTypeNameSize - structNameSize})
}
filteredFieldsShards = append(filteredFieldsShards, filteredFields)
filteredFields = nil
structNameSize = emptyStructTypeNameSize
}
filteredFields = append(filteredFields, field)
structNameSize += fieldTypeNameSize
}
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
}
ptrToStruct := false
if isStructPtr(field.Type) {
ptrToStruct = true
}
// Recurse into structs
if ptrToStruct || isStruct(field.Type) {
subMaxTypeNameSize := maxTypeNameSize
if maxTypeNameSize > 0 {
// In the worst case where only this nested struct will fit in the outer struct, the
// outer struct will contribute struct{}, the name and tag of the field that contains
// the nested struct, and one space before and after the field.
subMaxTypeNameSize -= emptyStructTypeNameSize + fieldToTypeNameSize(field, false) + 2
}
typ := field.Type
if ptrToStruct {
subMaxTypeNameSize -= len("*")
typ = typ.Elem()
}
nestedTypes, subFiltered := filterPropertyStruct(typ, subPrefix, subMaxTypeNameSize, predicate)
filtered = filtered || subFiltered
if nestedTypes == nil {
continue
}
for _, nestedType := range nestedTypes {
if ptrToStruct {
nestedType = reflect.PtrTo(nestedType)
}
field.Type = nestedType
appendAndShardIfNameFull(field)
}
} else {
appendAndShardIfNameFull(field)
}
}
if len(filteredFields) > 0 {
filteredFieldsShards = append(filteredFieldsShards, filteredFields)
}
return filteredFieldsShards, filtered
}
func fieldToTypeNameSize(field reflect.StructField, withType bool) int {
nameSize := len(field.Name)
nameSize += len(" ")
if withType {
nameSize += len(field.Type.String())
}
if field.Tag != "" {
nameSize += len(" ")
nameSize += len(strconv.Quote(string(field.Tag)))
}
return nameSize
}
// 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) {
filteredFieldsShards, filtered := filterPropertyStruct(prop, "", -1, predicate)
switch len(filteredFieldsShards) {
case 0:
return nil, filtered
case 1:
return filteredFieldsShards[0], filtered
default:
panic("filterPropertyStruct should only return 1 struct if maxNameSize < 0")
}
}
func filterPropertyStruct(prop reflect.Type, prefix string, maxNameSize int,
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))
}
filteredFieldsShards, filtered := filterPropertyStructFields(fields, prefix, maxNameSize, predicate)
if len(filteredFieldsShards) == 0 {
return nil, true
}
if !filtered {
if ptr {
return []reflect.Type{reflect.PtrTo(prop)}, false
}
return []reflect.Type{prop}, false
}
var ret []reflect.Type
for _, filteredFields := range filteredFieldsShards {
p := reflect.StructOf(filteredFields)
if ptr {
p = reflect.PtrTo(p)
}
ret = append(ret, p)
}
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, maxTypeNameSize int, predicate FilterFieldPredicate) (filteredProp []reflect.Type, filtered bool) {
return filterPropertyStruct(prop, "", maxTypeNameSize, predicate)
}