@@ -9,19 +9,19 @@ internal static class EntityMetaDataMerger
99#pragma warning disable CS0618 // Type or member is obsolete
1010 private static readonly Type [ ] _possibleBaseTypes = typeof ( LightAttributesBase ) . Assembly . GetTypes ( ) ;
1111#pragma warning restore CS0618 // Type or member is obsolete
12-
12+
1313 // We need to merge the previously saved metadata with the current metadata from HA
1414 // We do this because sometimes entities do not provide all their attributes,
1515 // like a Light only has a brightness attribute when it is turned on
16- //
16+ //
1717 // data structure:
1818 // [ {
1919 // "domain": "weather",
2020 // "isNumeric": false,
2121 // "entities": [ {
2222 // "id": "",
2323 // "friendlyName":""
24- // }]
24+ // }]
2525 // "attributes" : [ {
2626 // "jsonName": "temperature",
2727 // "cSharpName": "Temperature",
@@ -49,7 +49,7 @@ public static EntitiesMetaData Merge(CodeGenerationSettings codeGenerationSettin
4949 previous = SetBaseTypes ( previous ) ;
5050 current = SetBaseTypes ( current ) ;
5151 }
52-
52+
5353 return previous with
5454 {
5555 Domains = FullOuterJoin ( previous . Domains , current . Domains , p => ( p . Domain . ToLowerInvariant ( ) , p . IsNumeric ) , MergeDomains )
@@ -72,11 +72,11 @@ public static EntitiesMetaData SetBaseTypes(EntitiesMetaData entitiesMetaData)
7272 Domains = entitiesMetaData . Domains . Select ( m => DeriveFromBasetype ( m , _possibleBaseTypes ) ) . ToList ( )
7373 } ;
7474 }
75-
75+
7676 private static EntityDomainMetadata DeriveFromBasetype ( EntityDomainMetadata domainMetadata , IReadOnlyCollection < Type > possibleBaseTypes )
7777 {
7878 var baseType = possibleBaseTypes . FirstOrDefault ( t => t . Name == domainMetadata . AttributesClassName + "Base" ) ;
79-
79+
8080 var basePropertyJsonNames = baseType ? . GetProperties ( )
8181 . Select ( p => p . GetCustomAttribute < JsonPropertyNameAttribute > ( ) ? . Name ?? p . Name )
8282 . ToHashSet ( ) ?? new HashSet < string > ( ) ;
@@ -93,60 +93,57 @@ private static EntityDomainMetadata MergeDomains(EntityDomainMetadata previous,
9393 // Only keep entities from Current but merge the attributes
9494 return current with
9595 {
96- Attributes = MergeAttributeSets ( previous . Attributes , current . Attributes ) . ToList ( )
96+ Attributes = FullOuterJoin ( previous . Attributes , current . Attributes , a => a . JsonName , MergeAttributes ) . ToList ( )
9797 } ;
9898 }
9999
100- private static IEnumerable < EntityAttributeMetaData > MergeAttributeSets (
101- IReadOnlyCollection < EntityAttributeMetaData > previousAttributes ,
102- IEnumerable < EntityAttributeMetaData > currentAttributes )
103- {
104- return FullOuterJoin ( previousAttributes , currentAttributes , a => a . JsonName , MergeAttributes ) ;
105- }
106-
107100 private static EntityAttributeMetaData MergeAttributes ( EntityAttributeMetaData previous , EntityAttributeMetaData current )
108101 {
109102 // for Attributes matching by the JsonName keep the previous
110- // this makes sure the preferred CSharpName stays the same
103+ // this makes sure the preferred CSharpName stays the same, we only merge the types
111104 return previous with
112105 {
113- // In case the ClrType derived from the current metadata is not the same as the previous
114- // we will use 'object' for safety
115-
116- ClrType = previous . ClrType == current . ClrType ? previous . ClrType : typeof ( object ) ,
117- // There is a possible issue here when one of the ClrTypes is 'object'
118- // It can be object because either all inputs where JsonValueKind.Null, or there are
119- // multiple possible input types.
120- // Right here we dont actually know which it was, we will assume there were multiple,
121- // so the resulting type will stay object
106+ ClrType = MergeTypes ( previous . ClrType , current . ClrType )
122107 } ;
123108 }
124109
110+ private static Type ? MergeTypes ( Type ? previous , Type ? current )
111+ {
112+ // null for previous or current type means we did not get any non-null values to determine a type from
113+ // so if previous or current is null we use the other.
114+ // if for some reason the type has changed we use object to support both.
115+ return
116+ previous == current ? previous :
117+ previous is null ? current :
118+ current is null ? previous :
119+ typeof ( object ) ;
120+ }
121+
125122 private static EntityDomainMetadata HandleDuplicateCSharpNames ( EntityDomainMetadata entitiesMetaData )
126123 {
127124 // This hashset will initially have all Member names in the base class.
128- // We will then also add all new names to this set so we are sure they will all be unique
125+ // We will then also add all new names to this set so we are sure they will all be unique
129126 var reservedCSharpNames = entitiesMetaData . AttributesBaseClass ?
130127 . GetMembers ( ) . Select ( p => p . Name ) . ToHashSet ( ) ?? new HashSet < string > ( ) ;
131128
132129 var withDeDuplicatedCSharpNames = entitiesMetaData . Attributes
133130 . GroupBy ( t => t . CSharpName )
134131 . SelectMany ( s => DeDuplicateCSharpNames ( s . Key , s , reservedCSharpNames ) ) . ToList ( ) ;
135-
132+
136133 return entitiesMetaData with
137134 {
138135 Attributes = withDeDuplicatedCSharpNames
139136 } ;
140137 }
141-
138+
142139 private static IEnumerable < EntityAttributeMetaData > DeDuplicateCSharpNames (
143- string preferredCSharpName , IEnumerable < EntityAttributeMetaData > items ,
140+ string preferredCSharpName , IEnumerable < EntityAttributeMetaData > items ,
144141 ISet < string > reservedCSharpNames )
145142 {
146143 var list = items . ToList ( ) ;
147144 if ( list . Count == 1 && reservedCSharpNames . Add ( preferredCSharpName ) )
148145 {
149- // Just one Attribute with this preferredCSharpName AND it was not taken yet
146+ // Just one Attribute with this preferredCSharpName AND it was not taken yet
150147 return new [ ] { list [ 0 ] } ;
151148 }
152149
@@ -167,28 +164,28 @@ string ReserveNextAvailableName()
167164 }
168165
169166 /// <summary>
170- /// Full outer join two sets based on a key and merge the matches
167+ /// Full outer join two sets based on a key and merge the matches
171168 /// </summary>
172169 /// <returns>
173170 /// All items from previous and current that dont match
174171 /// A merged item for all matches based in the Merger delegate
175172 /// </returns>
176173 private static IEnumerable < T > FullOuterJoin < T , TKey > (
177174 IEnumerable < T > previous ,
178- IEnumerable < T > current ,
179- Func < T , TKey > keySelector ,
175+ IEnumerable < T > current ,
176+ Func < T , TKey > keySelector ,
180177 Func < T , T , T > merger ) where TKey : notnull
181178 {
182179 var previousLookup = previous . ToDictionary ( keySelector ) ;
183180 var currentLookup = current . ToLookup ( keySelector ) ;
184-
181+
185182 var inPrevious = previousLookup
186183 . Select ( p => ( previous : p . Value , current : currentLookup [ p . Key ] . FirstOrDefault ( ) ) )
187- . Select ( t => t . current == null
188- ? t . previous // Item in previous doe snot exist in current, return previous
184+ . Select ( t => t . current == null
185+ ? t . previous // Item in previous doe snot exist in current, return previous
189186 : merger ( t . previous , t . current ) ) ; // match, so call merge delegate
190-
187+
191188 var onlyInCurrent = currentLookup . Where ( l => ! previousLookup . ContainsKey ( l . Key ) ) . SelectMany ( l => l ) ;
192189 return inPrevious . Concat ( onlyInCurrent ) ;
193190 }
194- }
191+ }
0 commit comments