// 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) }