Skip to content

Commit

Permalink
Update importer and special types
Browse files Browse the repository at this point in the history
- 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:`)
  • Loading branch information
Gabriel-Darbord committed Mar 9, 2024
1 parent 47cbcc1 commit 8b0917f
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 55 deletions.
24 changes: 17 additions & 7 deletions src/Famix-Value-Importer/Collection.extension.st
Original file line number Diff line number Diff line change
Expand Up @@ -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' }
Expand Down
8 changes: 4 additions & 4 deletions src/Famix-Value-Importer/FamixJavaEntityFinder.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
28 changes: 18 additions & 10 deletions src/Famix-Value-Importer/FamixValueAbstractImporter.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -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!"
Expand Down
65 changes: 40 additions & 25 deletions src/Famix-Value-Importer/FamixValueJavaJacksonImporter.class.st
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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
]

Expand All @@ -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 ]
]

Expand All @@ -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 }
Expand Down
2 changes: 1 addition & 1 deletion src/Famix-Value-Importer/Integer.extension.st
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
8 changes: 7 additions & 1 deletion src/Famix-Value-Types/FamixValueJavaArray.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -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."

Expand All @@ -62,3 +62,9 @@ FamixValueJavaArray >> dimensions: anInteger [

dimensions := anInteger
]

{ #category : #importing }
FamixValueJavaArray >> import: rawValue on: importer [

self shouldNotImplement
]
19 changes: 14 additions & 5 deletions src/Famix-Value-Types/FamixValueJavaDate.class.st
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
29 changes: 29 additions & 0 deletions src/Famix-Value-Types/FamixValueJavaSQLDate.class.st
Original file line number Diff line number Diff line change
@@ -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
]
25 changes: 23 additions & 2 deletions src/Famix-Value-Types/FamixValueSpecialType.class.st
Original file line number Diff line number Diff line change
@@ -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.
"
Expand All @@ -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 [

Expand Down

0 comments on commit 8b0917f

Please sign in to comment.