1*1fa6dee9SAndroid Build Coastguard Worker// Copyright 2024 Google Inc. All rights reserved. 2*1fa6dee9SAndroid Build Coastguard Worker// 3*1fa6dee9SAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); 4*1fa6dee9SAndroid Build Coastguard Worker// you may not use this file except in compliance with the License. 5*1fa6dee9SAndroid Build Coastguard Worker// You may obtain a copy of the License at 6*1fa6dee9SAndroid Build Coastguard Worker// 7*1fa6dee9SAndroid Build Coastguard Worker// http://www.apache.org/licenses/LICENSE-2.0 8*1fa6dee9SAndroid Build Coastguard Worker// 9*1fa6dee9SAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*1fa6dee9SAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, 11*1fa6dee9SAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*1fa6dee9SAndroid Build Coastguard Worker// See the License for the specific language governing permissions and 13*1fa6dee9SAndroid Build Coastguard Worker// limitations under the License. 14*1fa6dee9SAndroid Build Coastguard Worker 15*1fa6dee9SAndroid Build Coastguard Workerpackage proptools 16*1fa6dee9SAndroid Build Coastguard Worker 17*1fa6dee9SAndroid Build Coastguard Workerimport ( 18*1fa6dee9SAndroid Build Coastguard Worker "fmt" 19*1fa6dee9SAndroid Build Coastguard Worker "reflect" 20*1fa6dee9SAndroid Build Coastguard Worker "slices" 21*1fa6dee9SAndroid Build Coastguard Worker 22*1fa6dee9SAndroid Build Coastguard Worker "github.com/google/blueprint/parser" 23*1fa6dee9SAndroid Build Coastguard Worker) 24*1fa6dee9SAndroid Build Coastguard Worker 25*1fa6dee9SAndroid Build Coastguard Workerfunc RepackProperties(props []interface{}) (*parser.Map, error) { 26*1fa6dee9SAndroid Build Coastguard Worker 27*1fa6dee9SAndroid Build Coastguard Worker var dereferencedProps []reflect.Value 28*1fa6dee9SAndroid Build Coastguard Worker for _, rawProp := range props { 29*1fa6dee9SAndroid Build Coastguard Worker propStruct := reflect.ValueOf(rawProp) 30*1fa6dee9SAndroid Build Coastguard Worker if !isStructPtr(propStruct.Type()) { 31*1fa6dee9SAndroid Build Coastguard Worker return nil, fmt.Errorf("properties must be *struct, got %s", 32*1fa6dee9SAndroid Build Coastguard Worker propStruct.Type()) 33*1fa6dee9SAndroid Build Coastguard Worker } 34*1fa6dee9SAndroid Build Coastguard Worker propStruct = propStruct.Elem() 35*1fa6dee9SAndroid Build Coastguard Worker dereferencedProps = append(dereferencedProps, propStruct) 36*1fa6dee9SAndroid Build Coastguard Worker } 37*1fa6dee9SAndroid Build Coastguard Worker 38*1fa6dee9SAndroid Build Coastguard Worker return repackStructs(dereferencedProps) 39*1fa6dee9SAndroid Build Coastguard Worker} 40*1fa6dee9SAndroid Build Coastguard Worker 41*1fa6dee9SAndroid Build Coastguard Workerfunc repackStructs(props []reflect.Value) (*parser.Map, error) { 42*1fa6dee9SAndroid Build Coastguard Worker var allFieldNames []string 43*1fa6dee9SAndroid Build Coastguard Worker for _, prop := range props { 44*1fa6dee9SAndroid Build Coastguard Worker propType := prop.Type() 45*1fa6dee9SAndroid Build Coastguard Worker for i := 0; i < propType.NumField(); i++ { 46*1fa6dee9SAndroid Build Coastguard Worker field := propType.Field(i) 47*1fa6dee9SAndroid Build Coastguard Worker if !slices.Contains(allFieldNames, field.Name) { 48*1fa6dee9SAndroid Build Coastguard Worker allFieldNames = append(allFieldNames, field.Name) 49*1fa6dee9SAndroid Build Coastguard Worker } 50*1fa6dee9SAndroid Build Coastguard Worker } 51*1fa6dee9SAndroid Build Coastguard Worker } 52*1fa6dee9SAndroid Build Coastguard Worker 53*1fa6dee9SAndroid Build Coastguard Worker result := &parser.Map{} 54*1fa6dee9SAndroid Build Coastguard Worker 55*1fa6dee9SAndroid Build Coastguard Worker for _, fieldName := range allFieldNames { 56*1fa6dee9SAndroid Build Coastguard Worker var fields []reflect.Value 57*1fa6dee9SAndroid Build Coastguard Worker for _, prop := range props { 58*1fa6dee9SAndroid Build Coastguard Worker field := prop.FieldByName(fieldName) 59*1fa6dee9SAndroid Build Coastguard Worker if field.IsValid() { 60*1fa6dee9SAndroid Build Coastguard Worker fields = append(fields, field) 61*1fa6dee9SAndroid Build Coastguard Worker } 62*1fa6dee9SAndroid Build Coastguard Worker } 63*1fa6dee9SAndroid Build Coastguard Worker if err := assertFieldsEquivalent(fields); err != nil { 64*1fa6dee9SAndroid Build Coastguard Worker return nil, err 65*1fa6dee9SAndroid Build Coastguard Worker } 66*1fa6dee9SAndroid Build Coastguard Worker 67*1fa6dee9SAndroid Build Coastguard Worker var expr parser.Expression 68*1fa6dee9SAndroid Build Coastguard Worker var field reflect.Value 69*1fa6dee9SAndroid Build Coastguard Worker for _, f := range fields { 70*1fa6dee9SAndroid Build Coastguard Worker if !isPropEmpty(f) { 71*1fa6dee9SAndroid Build Coastguard Worker field = f 72*1fa6dee9SAndroid Build Coastguard Worker break 73*1fa6dee9SAndroid Build Coastguard Worker } 74*1fa6dee9SAndroid Build Coastguard Worker } 75*1fa6dee9SAndroid Build Coastguard Worker if !field.IsValid() { 76*1fa6dee9SAndroid Build Coastguard Worker continue 77*1fa6dee9SAndroid Build Coastguard Worker } 78*1fa6dee9SAndroid Build Coastguard Worker if isStruct(field.Type()) && !isConfigurable(field.Type()) { 79*1fa6dee9SAndroid Build Coastguard Worker x, err := repackStructs(fields) 80*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 81*1fa6dee9SAndroid Build Coastguard Worker return nil, err 82*1fa6dee9SAndroid Build Coastguard Worker } 83*1fa6dee9SAndroid Build Coastguard Worker if x != nil { 84*1fa6dee9SAndroid Build Coastguard Worker expr = x 85*1fa6dee9SAndroid Build Coastguard Worker } 86*1fa6dee9SAndroid Build Coastguard Worker } else { 87*1fa6dee9SAndroid Build Coastguard Worker x, err := fieldToExpr(field) 88*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 89*1fa6dee9SAndroid Build Coastguard Worker return nil, err 90*1fa6dee9SAndroid Build Coastguard Worker } 91*1fa6dee9SAndroid Build Coastguard Worker if x != nil { 92*1fa6dee9SAndroid Build Coastguard Worker expr = *x 93*1fa6dee9SAndroid Build Coastguard Worker } 94*1fa6dee9SAndroid Build Coastguard Worker } 95*1fa6dee9SAndroid Build Coastguard Worker 96*1fa6dee9SAndroid Build Coastguard Worker if expr != nil { 97*1fa6dee9SAndroid Build Coastguard Worker result.Properties = append(result.Properties, &parser.Property{ 98*1fa6dee9SAndroid Build Coastguard Worker Name: PropertyNameForField(fieldName), 99*1fa6dee9SAndroid Build Coastguard Worker Value: expr, 100*1fa6dee9SAndroid Build Coastguard Worker }) 101*1fa6dee9SAndroid Build Coastguard Worker } 102*1fa6dee9SAndroid Build Coastguard Worker } 103*1fa6dee9SAndroid Build Coastguard Worker 104*1fa6dee9SAndroid Build Coastguard Worker return result, nil 105*1fa6dee9SAndroid Build Coastguard Worker} 106*1fa6dee9SAndroid Build Coastguard Worker 107*1fa6dee9SAndroid Build Coastguard Workerfunc fieldToExpr(field reflect.Value) (*parser.Expression, error) { 108*1fa6dee9SAndroid Build Coastguard Worker if IsConfigurable(field.Type()) { 109*1fa6dee9SAndroid Build Coastguard Worker return field.Interface().(configurableReflection).toExpression() 110*1fa6dee9SAndroid Build Coastguard Worker } 111*1fa6dee9SAndroid Build Coastguard Worker if field.Kind() == reflect.Pointer { 112*1fa6dee9SAndroid Build Coastguard Worker if field.IsNil() { 113*1fa6dee9SAndroid Build Coastguard Worker return nil, nil 114*1fa6dee9SAndroid Build Coastguard Worker } 115*1fa6dee9SAndroid Build Coastguard Worker field = field.Elem() 116*1fa6dee9SAndroid Build Coastguard Worker } 117*1fa6dee9SAndroid Build Coastguard Worker switch field.Kind() { 118*1fa6dee9SAndroid Build Coastguard Worker case reflect.String: 119*1fa6dee9SAndroid Build Coastguard Worker var result parser.Expression = &parser.String{Value: field.String()} 120*1fa6dee9SAndroid Build Coastguard Worker return &result, nil 121*1fa6dee9SAndroid Build Coastguard Worker case reflect.Bool: 122*1fa6dee9SAndroid Build Coastguard Worker var result parser.Expression = &parser.Bool{Value: field.Bool()} 123*1fa6dee9SAndroid Build Coastguard Worker return &result, nil 124*1fa6dee9SAndroid Build Coastguard Worker case reflect.Int, reflect.Int64: 125*1fa6dee9SAndroid Build Coastguard Worker var result parser.Expression = &parser.Int64{Value: field.Int()} 126*1fa6dee9SAndroid Build Coastguard Worker return &result, nil 127*1fa6dee9SAndroid Build Coastguard Worker case reflect.Slice: 128*1fa6dee9SAndroid Build Coastguard Worker var contents []parser.Expression 129*1fa6dee9SAndroid Build Coastguard Worker for i := 0; i < field.Len(); i++ { 130*1fa6dee9SAndroid Build Coastguard Worker inner, err := fieldToExpr(field.Index(i)) 131*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 132*1fa6dee9SAndroid Build Coastguard Worker return nil, err 133*1fa6dee9SAndroid Build Coastguard Worker } 134*1fa6dee9SAndroid Build Coastguard Worker contents = append(contents, *inner) 135*1fa6dee9SAndroid Build Coastguard Worker } 136*1fa6dee9SAndroid Build Coastguard Worker var result parser.Expression = &parser.List{Values: contents} 137*1fa6dee9SAndroid Build Coastguard Worker return &result, nil 138*1fa6dee9SAndroid Build Coastguard Worker case reflect.Struct: 139*1fa6dee9SAndroid Build Coastguard Worker var properties []*parser.Property 140*1fa6dee9SAndroid Build Coastguard Worker typ := field.Type() 141*1fa6dee9SAndroid Build Coastguard Worker for i := 0; i < typ.NumField(); i++ { 142*1fa6dee9SAndroid Build Coastguard Worker inner, err := fieldToExpr(field.Field(i)) 143*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 144*1fa6dee9SAndroid Build Coastguard Worker return nil, err 145*1fa6dee9SAndroid Build Coastguard Worker } 146*1fa6dee9SAndroid Build Coastguard Worker properties = append(properties, &parser.Property{ 147*1fa6dee9SAndroid Build Coastguard Worker Name: typ.Field(i).Name, 148*1fa6dee9SAndroid Build Coastguard Worker Value: *inner, 149*1fa6dee9SAndroid Build Coastguard Worker }) 150*1fa6dee9SAndroid Build Coastguard Worker } 151*1fa6dee9SAndroid Build Coastguard Worker var result parser.Expression = &parser.Map{Properties: properties} 152*1fa6dee9SAndroid Build Coastguard Worker return &result, nil 153*1fa6dee9SAndroid Build Coastguard Worker default: 154*1fa6dee9SAndroid Build Coastguard Worker return nil, fmt.Errorf("Unhandled type: %s", field.Kind().String()) 155*1fa6dee9SAndroid Build Coastguard Worker } 156*1fa6dee9SAndroid Build Coastguard Worker} 157*1fa6dee9SAndroid Build Coastguard Worker 158*1fa6dee9SAndroid Build Coastguard Workerfunc isPropEmpty(value reflect.Value) bool { 159*1fa6dee9SAndroid Build Coastguard Worker switch value.Kind() { 160*1fa6dee9SAndroid Build Coastguard Worker case reflect.Pointer: 161*1fa6dee9SAndroid Build Coastguard Worker if value.IsNil() { 162*1fa6dee9SAndroid Build Coastguard Worker return true 163*1fa6dee9SAndroid Build Coastguard Worker } 164*1fa6dee9SAndroid Build Coastguard Worker return isPropEmpty(value.Elem()) 165*1fa6dee9SAndroid Build Coastguard Worker case reflect.Struct: 166*1fa6dee9SAndroid Build Coastguard Worker if isConfigurable(value.Type()) { 167*1fa6dee9SAndroid Build Coastguard Worker return value.Interface().(configurableReflection).isEmpty() 168*1fa6dee9SAndroid Build Coastguard Worker } 169*1fa6dee9SAndroid Build Coastguard Worker for i := 0; i < value.NumField(); i++ { 170*1fa6dee9SAndroid Build Coastguard Worker if !isPropEmpty(value.Field(i)) { 171*1fa6dee9SAndroid Build Coastguard Worker return false 172*1fa6dee9SAndroid Build Coastguard Worker } 173*1fa6dee9SAndroid Build Coastguard Worker } 174*1fa6dee9SAndroid Build Coastguard Worker return true 175*1fa6dee9SAndroid Build Coastguard Worker default: 176*1fa6dee9SAndroid Build Coastguard Worker return false 177*1fa6dee9SAndroid Build Coastguard Worker } 178*1fa6dee9SAndroid Build Coastguard Worker} 179*1fa6dee9SAndroid Build Coastguard Worker 180*1fa6dee9SAndroid Build Coastguard Workerfunc assertFieldsEquivalent(fields []reflect.Value) error { 181*1fa6dee9SAndroid Build Coastguard Worker var firstNonEmpty reflect.Value 182*1fa6dee9SAndroid Build Coastguard Worker var firstIndex int 183*1fa6dee9SAndroid Build Coastguard Worker for i, f := range fields { 184*1fa6dee9SAndroid Build Coastguard Worker if !isPropEmpty(f) { 185*1fa6dee9SAndroid Build Coastguard Worker firstNonEmpty = f 186*1fa6dee9SAndroid Build Coastguard Worker firstIndex = i 187*1fa6dee9SAndroid Build Coastguard Worker break 188*1fa6dee9SAndroid Build Coastguard Worker } 189*1fa6dee9SAndroid Build Coastguard Worker } 190*1fa6dee9SAndroid Build Coastguard Worker if !firstNonEmpty.IsValid() { 191*1fa6dee9SAndroid Build Coastguard Worker return nil 192*1fa6dee9SAndroid Build Coastguard Worker } 193*1fa6dee9SAndroid Build Coastguard Worker for i, f := range fields { 194*1fa6dee9SAndroid Build Coastguard Worker if i != firstIndex && !isPropEmpty(f) { 195*1fa6dee9SAndroid Build Coastguard Worker if err := assertTwoNonEmptyFieldsEquivalent(firstNonEmpty, f); err != nil { 196*1fa6dee9SAndroid Build Coastguard Worker return err 197*1fa6dee9SAndroid Build Coastguard Worker } 198*1fa6dee9SAndroid Build Coastguard Worker } 199*1fa6dee9SAndroid Build Coastguard Worker } 200*1fa6dee9SAndroid Build Coastguard Worker return nil 201*1fa6dee9SAndroid Build Coastguard Worker} 202*1fa6dee9SAndroid Build Coastguard Worker 203*1fa6dee9SAndroid Build Coastguard Workerfunc assertTwoNonEmptyFieldsEquivalent(a, b reflect.Value) error { 204*1fa6dee9SAndroid Build Coastguard Worker aType := a.Type() 205*1fa6dee9SAndroid Build Coastguard Worker bType := b.Type() 206*1fa6dee9SAndroid Build Coastguard Worker 207*1fa6dee9SAndroid Build Coastguard Worker if aType != bType { 208*1fa6dee9SAndroid Build Coastguard Worker return fmt.Errorf("fields must have the same type") 209*1fa6dee9SAndroid Build Coastguard Worker } 210*1fa6dee9SAndroid Build Coastguard Worker 211*1fa6dee9SAndroid Build Coastguard Worker switch aType.Kind() { 212*1fa6dee9SAndroid Build Coastguard Worker case reflect.Pointer: 213*1fa6dee9SAndroid Build Coastguard Worker return assertTwoNonEmptyFieldsEquivalent(a.Elem(), b.Elem()) 214*1fa6dee9SAndroid Build Coastguard Worker case reflect.String: 215*1fa6dee9SAndroid Build Coastguard Worker if a.String() != b.String() { 216*1fa6dee9SAndroid Build Coastguard Worker return fmt.Errorf("Conflicting fields in property structs had values %q and %q", a.String(), b.String()) 217*1fa6dee9SAndroid Build Coastguard Worker } 218*1fa6dee9SAndroid Build Coastguard Worker case reflect.Bool: 219*1fa6dee9SAndroid Build Coastguard Worker if a.Bool() != b.Bool() { 220*1fa6dee9SAndroid Build Coastguard Worker return fmt.Errorf("Conflicting fields in property structs had values %t and %t", a.Bool(), b.Bool()) 221*1fa6dee9SAndroid Build Coastguard Worker } 222*1fa6dee9SAndroid Build Coastguard Worker case reflect.Slice: 223*1fa6dee9SAndroid Build Coastguard Worker if a.Len() != b.Len() { 224*1fa6dee9SAndroid Build Coastguard Worker return fmt.Errorf("Conflicting fields in property structs had lengths %d and %d", a.Len(), b.Len()) 225*1fa6dee9SAndroid Build Coastguard Worker } 226*1fa6dee9SAndroid Build Coastguard Worker for i := 0; i < a.Len(); i++ { 227*1fa6dee9SAndroid Build Coastguard Worker if err := assertTwoNonEmptyFieldsEquivalent(a.Index(i), b.Index(i)); err != nil { 228*1fa6dee9SAndroid Build Coastguard Worker return err 229*1fa6dee9SAndroid Build Coastguard Worker } 230*1fa6dee9SAndroid Build Coastguard Worker } 231*1fa6dee9SAndroid Build Coastguard Worker case reflect.Int: 232*1fa6dee9SAndroid Build Coastguard Worker if a.Int() != b.Int() { 233*1fa6dee9SAndroid Build Coastguard Worker return fmt.Errorf("Conflicting fields in property structs had values %d and %d", a.Int(), b.Int()) 234*1fa6dee9SAndroid Build Coastguard Worker } 235*1fa6dee9SAndroid Build Coastguard Worker case reflect.Struct: 236*1fa6dee9SAndroid Build Coastguard Worker if isConfigurable(a.Type()) { 237*1fa6dee9SAndroid Build Coastguard Worker // We could properly check that two configurables are equivalent, but that's a lot more 238*1fa6dee9SAndroid Build Coastguard Worker // work for a case that I don't think should show up in practice. 239*1fa6dee9SAndroid Build Coastguard Worker return fmt.Errorf("Cannot handle two property structs with nonempty configurable properties") 240*1fa6dee9SAndroid Build Coastguard Worker } 241*1fa6dee9SAndroid Build Coastguard Worker // We don't care about checking if structs are equivalent, we'll check their individual 242*1fa6dee9SAndroid Build Coastguard Worker // fields when we recurse down. 243*1fa6dee9SAndroid Build Coastguard Worker default: 244*1fa6dee9SAndroid Build Coastguard Worker return fmt.Errorf("Unhandled kind: %s", aType.Kind().String()) 245*1fa6dee9SAndroid Build Coastguard Worker } 246*1fa6dee9SAndroid Build Coastguard Worker 247*1fa6dee9SAndroid Build Coastguard Worker return nil 248*1fa6dee9SAndroid Build Coastguard Worker} 249