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