From 8b0917ff46f754e40bc875586f5a6cc3679fcd94 Mon Sep 17 00:00:00 2001 From: Gabriel Darbord Date: Sat, 9 Mar 2024 09:18:45 +0100 Subject: [PATCH] Update importer and special types - Extract method for importing object attributes - JavaEntityFinder searches in inner types - Read enums from Jackson lists - Move special types import method to their class instead of the importer - Initialize special types and map them to their fully qualified name - Add `java.sql.Date` special date - Remove dead code (`isDictionary:`) --- .../Collection.extension.st | 24 +++++-- .../FamixJavaEntityFinder.class.st | 8 +-- .../FamixValueAbstractImporter.class.st | 28 +++++--- .../FamixValueJavaJacksonImporter.class.st | 65 ++++++++++++------- src/Famix-Value-Importer/Integer.extension.st | 2 +- .../FamixValueJavaArray.class.st | 8 ++- .../FamixValueJavaDate.class.st | 19 ++++-- .../FamixValueJavaSQLDate.class.st | 29 +++++++++ .../FamixValueSpecialType.class.st | 25 ++++++- 9 files changed, 153 insertions(+), 55 deletions(-) create mode 100644 src/Famix-Value-Types/FamixValueJavaSQLDate.class.st diff --git a/src/Famix-Value-Importer/Collection.extension.st b/src/Famix-Value-Importer/Collection.extension.st index 2fd6b34..8dfcd4f 100644 --- a/src/Famix-Value-Importer/Collection.extension.st +++ b/src/Famix-Value-Importer/Collection.extension.st @@ -4,13 +4,23 @@ Extension { #name : #Collection } Collection >> asJavaJacksonValueOn: importer [ "List values are serialized as an array containing [1] the type name and [2] the array of elements." - ^ self hasJavaJacksonTypeInformation - ifTrue: [ - importer - importValueFromList: (self at: 2) - of: (importer loadTypeNamed: (self at: 1)) ] - ifFalse: [ - importer importValueFromList: self of: importer loadType ] + | type | + type := importer entityFinder findTypeNamed: (self at: 1). + + "might be an evident Jackson TypeInfo wrapper" + self hasJavaJacksonTypeInformation ifTrue: [ + ^ importer importValueFromList: (self at: 2) of: type ]. + + "might be an enumeration value" + (type usesFamixTrait: FamixTEnum) ifTrue: [ + ^ importer importEnumValue: (self at: 2) of: type ]. + + "might be a special object" + (importer importSpecialObject: (self at: 2) of: type) ifNotNil: [ + :value | ^ value ]. + + "must be a regular list" + ^ importer importValueFromList: self of: importer loadType ] { #category : #'*Famix-Value-Importer' } diff --git a/src/Famix-Value-Importer/FamixJavaEntityFinder.class.st b/src/Famix-Value-Importer/FamixJavaEntityFinder.class.st index 3d0bb0e..c688b04 100644 --- a/src/Famix-Value-Importer/FamixJavaEntityFinder.class.st +++ b/src/Famix-Value-Importer/FamixJavaEntityFinder.class.st @@ -43,15 +43,15 @@ FamixJavaEntityFinder >> parseTypeName: name [ dimensions := name occurrencesOf: $[. type := (typeName includes: $.) ifFalse: [ self findTypeWithName: typeName ] - ifTrue: [ + ifTrue: [ "Search by translating the type name to a mooseName." self findTypeWithMooseName: ((typeName allButLast: dimensions * 2) - copyReplaceAll: '.' - with: '::') ]. + copyWithRegex: '\.|\$' + matchesReplacedWith: '::') ]. ^ dimensions > 0 ifFalse: [ type ] ifTrue: [ - (FamixValueJavaArray proxying: type) dimensions: dimensions ] + (FamixValueJavaArray wrapping: type) dimensions: dimensions ] ] { #category : #matching } diff --git a/src/Famix-Value-Importer/FamixValueAbstractImporter.class.st b/src/Famix-Value-Importer/FamixValueAbstractImporter.class.st index 34ae2b3..93e4f01 100644 --- a/src/Famix-Value-Importer/FamixValueAbstractImporter.class.st +++ b/src/Famix-Value-Importer/FamixValueAbstractImporter.class.st @@ -108,22 +108,30 @@ FamixValueAbstractImporter >> importEnumValue: rawValue of: type [ { #category : #importing } FamixValueAbstractImporter >> importObject: rawObject of: type [ - | object attribute | + | object | object := self model newOfObject type: type. (self getObjectIdentity: rawObject) ifNotNil: [ :id | objectDict at: id put: object ]. - rawObject associationsDo: [ :assoc | - attribute := type findAttributeNamed: assoc key. - self - withTypeInference: (attribute ifNotNil: [ attribute declaredType ]) - do: [ - self model newOfObjectAttribute - value: (self importValue: assoc value); - object: object; - attribute: attribute ] ]. + rawObject keysAndValuesDo: [ :name :value | + (self importObjectAttribute: value of: type named: name) ifNotNil: [ + :attribute | attribute object: object ] ]. ^ object ] +{ #category : #importing } +FamixValueAbstractImporter >> importObjectAttribute: rawValue of: type named: name [ + + | attribute | + attribute := type findAttributeNamed: name. + ^ self + withTypeInference: + (attribute ifNotNil: [ attribute declaredType ]) + do: [ + self model newOfObjectAttribute + value: (self importValue: rawValue); + attribute: attribute ] +] + { #category : #importing } FamixValueAbstractImporter >> importPrimitive: rawValue of: typeName [ "Expect to be given a type name instead of a type!" diff --git a/src/Famix-Value-Importer/FamixValueJavaJacksonImporter.class.st b/src/Famix-Value-Importer/FamixValueJavaJacksonImporter.class.st index 53d368a..53b4c85 100644 --- a/src/Famix-Value-Importer/FamixValueJavaJacksonImporter.class.st +++ b/src/Famix-Value-Importer/FamixValueJavaJacksonImporter.class.st @@ -1,6 +1,15 @@ " I am a FamixValue importer for Java. -I understand the JSON format from the Jackson library, which must be configured to pair each value with the fully qualified name of its type. +I understand the JSON format of the Jackson library up to and including version `2.10`. +The ObjectMapper must be configured to associate each value with the fully qualified name of its type. + +```language=text +@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = ""@id"") +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = ""@type"") +interface MixIn {} + +new ObjectMapper().addMixIn(Object.class, MixIn.class); +``` " Class { #name : #FamixValueJavaJacksonImporter, @@ -50,25 +59,30 @@ FamixValueJavaJacksonImporter >> importClassReference: rawValue of: type [ ] { #category : #importing } -FamixValueJavaJacksonImporter >> importDateObject: rawValue of: type [ - - ^ self model newOfObject - type: (self specialTypes - at: type name - ifAbsentPut: [ FamixValueJavaDate proxying: type ]); - addValue: - (model newOfObjectAttribute value: (model newOfPrimitiveType - value: rawValue; - type: (self entityFinder findTypeNamed: 'long'))); - yourself +FamixValueJavaJacksonImporter >> importObjectAttribute: rawValue of: type named: name [ + "Skip transient attributes." + + | attribute | + (attribute := type findAttributeNamed: name) ifNotNil: [ + attribute isTransient ifTrue: [ ^ nil ] ]. + ^ self + withTypeInference: + (attribute ifNotNil: [ attribute declaredType ]) + do: [ + self model newOfObjectAttribute + value: (self importValue: rawValue); + attribute: attribute ] ] { #category : #importing } FamixValueJavaJacksonImporter >> importSpecialObject: rawValue of: type [ - "Some objects have special rules for serialization" + "Some objects have special rules for serialization. + They should have a corresponding type wrapper that knows how to handle them." - (#( Date Timestamp ) includes: type name) ifTrue: [ - ^ self importDateObject: rawValue of: type ]. + self specialTypes + at: type mooseNameWithDots + ifPresent: [ :typeWrapper | + ^ typeWrapper import: rawValue wrapping: type on: self ]. ^ nil ] @@ -90,26 +104,22 @@ FamixValueJavaJacksonImporter >> importValueFromList: rawValue of: type [ ^ self model newOfUnknownType value: rawValue; type: type ]. - (type class usesTrait: FamixTEnum) ifTrue: [ + (type usesFamixTrait: FamixTEnum) ifTrue: [ ^ self importEnumValue: rawValue of: type ]. (self isPrimitiveType: type) ifTrue: [ ^ self model newOfPrimitiveType value: rawValue; type: type ]. - type isClass ifTrue: [ ^ self importSpecialObject: rawValue of: type ]. + type isClass ifTrue: [ "might be a special object" + (self importSpecialObject: rawValue of: type) ifNotNil: [ :object | + ^ object ] ]. self error: 'Unknown type: ' , type mooseNameWithDots ] -{ #category : #'private - utility' } -FamixValueJavaJacksonImporter >> isDictionary: type [ - - ^ #( Map HashMap LinkedHashMap ) includes: type name -] - { #category : #'private - utility' } FamixValueJavaJacksonImporter >> isPrimitiveType: type [ - ^ type isPrimitiveType or: [ + ^ type isPrimitiveType or: [ "Classes wrapping primitives are also considered primitives." #( Integer Float Double Long Short Byte ) includes: type name ] ] @@ -127,7 +137,12 @@ FamixValueJavaJacksonImporter >> parseList: serializedValues [ { #category : #accessing } FamixValueJavaJacksonImporter >> specialTypes [ - ^ specialTypes ifNil: [ specialTypes := Dictionary new ] + ^ specialTypes ifNil: [ + specialTypes := Dictionary new. + specialTypes at: 'java.util.Date' put: FamixValueJavaDate. + specialTypes at: 'java.sql.Timestamp' put: FamixValueJavaDate. + specialTypes at: 'java.sql.Date' put: FamixValueJavaSQLDate. + specialTypes ] ] { #category : #accessing } diff --git a/src/Famix-Value-Importer/Integer.extension.st b/src/Famix-Value-Importer/Integer.extension.st index 57b5c2f..581dec5 100644 --- a/src/Famix-Value-Importer/Integer.extension.st +++ b/src/Famix-Value-Importer/Integer.extension.st @@ -8,7 +8,7 @@ Integer >> asJavaJacksonValueOn: importer [ (importer isPrimitiveType: typeInference) not ]) ifTrue: [ "object reference, solved by the importer" (importer getObjectFromIdentity: self - ifAbsent: [ "*might* be a special object with missing runtime type information" + ifAbsent: [ "might be a special object with missing runtime type information" importer importSpecialObject: self of: typeInference ]) ifNotNil: [ :value | ^ value ] ]. "if none of the above holds true, this must be a regular integer" diff --git a/src/Famix-Value-Types/FamixValueJavaArray.class.st b/src/Famix-Value-Types/FamixValueJavaArray.class.st index b77f53e..5d8bcba 100644 --- a/src/Famix-Value-Types/FamixValueJavaArray.class.st +++ b/src/Famix-Value-Types/FamixValueJavaArray.class.st @@ -40,7 +40,7 @@ FamixValueJavaArray >> acceptValueVisitor: visitor forCollection: array [ yourself ] -{ #category : #'as yet unclassified' } +{ #category : #visiting } FamixValueJavaArray >> decorate: aFamixJavaType asFASTJavaTypeExpressionOn: visitor [ "Add array brackets to the type name." @@ -62,3 +62,9 @@ FamixValueJavaArray >> dimensions: anInteger [ dimensions := anInteger ] + +{ #category : #importing } +FamixValueJavaArray >> import: rawValue on: importer [ + + self shouldNotImplement +] diff --git a/src/Famix-Value-Types/FamixValueJavaDate.class.st b/src/Famix-Value-Types/FamixValueJavaDate.class.st index 625c8b7..db0005e 100644 --- a/src/Famix-Value-Types/FamixValueJavaDate.class.st +++ b/src/Famix-Value-Types/FamixValueJavaDate.class.st @@ -1,25 +1,34 @@ " -A representation of Java date types like `java.util.Date` or `java.sql.Timestamp`. -This family of types has a special serialization format: all attributes are transient, and only a `long` is used to measure the epoch time. +A representation of Java date types such as `java.util.Date` or `java.sql.Timestamp`. +This family of types has a special serialization format: all attributes are transient, and only a `long` is used to measure the distance to the epoch in milliseconds. " Class { #name : #FamixValueJavaDate, - #superclass : #FamixValueSpecialType, + #superclass : #FamixValueSpecialClass, #category : #'Famix-Value-Types' } { #category : #visiting } FamixValueJavaDate >> acceptValueVisitor: visitor forObject: object [ - "Skip visiting the attribute, it is added as a constructor argument" + "Skip visiting the attribute, it is added as a constructor argument." ^ visitor statementBlock addStatement: (visitor makeVarDeclStatement: object); yourself ] +{ #category : #importing } +FamixValueJavaDate >> importSpecial: rawValue on: importer [ + "The value is the distance to the epoch in milliseconds, represented with a long." + + ^ importer model newOfPrimitiveType + value: rawValue; + type: (importer entityFinder findTypeNamed: 'long') +] + { #category : #converting } FamixValueJavaDate >> value: date asFASTJavaExpressionOn: visitor [ - "Add epoch time attribute as constructor argument" + "Add epoch time attribute as constructor argument." ^ (visitor makeNewExpression: date) addArgument: (visitor model newLongLiteral primitiveValue: diff --git a/src/Famix-Value-Types/FamixValueJavaSQLDate.class.st b/src/Famix-Value-Types/FamixValueJavaSQLDate.class.st new file mode 100644 index 0000000..4b604f1 --- /dev/null +++ b/src/Famix-Value-Types/FamixValueJavaSQLDate.class.st @@ -0,0 +1,29 @@ +" +A representation of the `java.sql.Date` type which is serialized using a String representation. +" +Class { + #name : #FamixValueJavaSQLDate, + #superclass : #FamixValueJavaDate, + #category : #'Famix-Value-Types' +} + +{ #category : #importing } +FamixValueJavaSQLDate >> importSpecial: rawValue on: importer [ + "The value is the string representation in 'yyyy-mm-dd' format." + + ^ importer model newOfPrimitiveType + value: rawValue; + type: (importer entityFinder findTypeNamed: 'String') +] + +{ #category : #converting } +FamixValueJavaSQLDate >> value: date asFASTJavaExpressionOn: visitor [ + "Call java.sql.Date#valueOf(String) to recreate this date." + + ^ visitor model newMethodInvocation + receiver: (FASTJavaBuilder current referType: type); + name: 'valueOf'; + addArgument: (visitor model newStringLiteral primitiveValue: + date value first value value); + yourself +] diff --git a/src/Famix-Value-Types/FamixValueSpecialType.class.st b/src/Famix-Value-Types/FamixValueSpecialType.class.st index 3b592c1..5cf20fa 100644 --- a/src/Famix-Value-Types/FamixValueSpecialType.class.st +++ b/src/Famix-Value-Types/FamixValueSpecialType.class.st @@ -1,7 +1,7 @@ " FamixValueSpecialType is an abstract superclass that represents a type in any programming language that requires custom handling due to its unique characteristics or serialization format. -These particular types are proxied using encapsulation, enabling customized importing and exporting mechanisms. +These special types are wrapped using encapsulation, allowing for custom import and export mechanisms. My subclasses are responsible for implementing the specific behavior required to handle special types in a particular language. " @@ -15,17 +15,38 @@ Class { } { #category : #'instance creation' } -FamixValueSpecialType class >> proxying: aFamixTType [ +FamixValueSpecialType class >> import: rawValue wrapping: type on: importer [ + "Wrap an object and use custom import mechanism." + + ^ (self new type: type) import: rawValue on: importer +] + +{ #category : #testing } +FamixValueSpecialType class >> isAbstract [ + + ^ self == FamixValueSpecialType +] + +{ #category : #'instance creation' } +FamixValueSpecialType class >> wrapping: aFamixTType [ + "Wrap an object for custom import/export mechanisms." ^ self new type: aFamixTType ] { #category : #'reflective operations' } FamixValueSpecialType >> doesNotUnderstand: aMessage [ + "Defer to the wrapped type." ^ type perform: aMessage selector withArguments: aMessage arguments ] +{ #category : #importing } +FamixValueSpecialType >> import: rawValue on: importer [ + + self subclassResponsibility +] + { #category : #accessing } FamixValueSpecialType >> type [