@@ -2,15 +2,38 @@ import * as fs from "fs-extra";
2
2
import * as jsonConfig from "./annotationConfig" ;
3
3
import { ConfigParseError } from "./configParseError" ;
4
4
import * as model from "./model" ;
5
+ import { getBeanById } from "./parserDriver" ;
5
6
import { lowerInitialChar } from "./utils" ;
6
7
7
8
interface NamedComponent {
8
9
qualifiedClassName : string ;
9
10
component : jsonConfig . Component ;
10
11
}
11
12
12
- const beansById = new Map < string , NamedComponent > ( ) ;
13
+ /**
14
+ * [[BeanDefinitionMethod]] does not include the name of the method or the name of the class it is defined in; these are provided by the context.
15
+ *
16
+ * This class therefore combines these bits of information to act as the value to be used in a map from calculated identifiers to bean definition
17
+ * methods.
18
+ */
19
+ interface NamedBeanDefinitionMethod {
20
+ className : string ;
21
+ name : string ;
22
+ method : jsonConfig . BeanDefinitionMethod ;
23
+ }
24
+
25
+ // A map from identifiers to components discovered in the JSON file.
26
+ const componentsById = new Map < string , NamedComponent > ( ) ;
27
+ // A map from identifiers to bean definition methods discovered in the JSON file.
28
+ const beansById = new Map < string , NamedBeanDefinitionMethod > ( ) ;
13
29
30
+ /**
31
+ * Collect beans from the JSON configuration file without parsing them.
32
+ *
33
+ * This allows us to set up objects that can be used to resolve circular references discovered later during parsing.
34
+ * @param filePath The path to the configuration file to read the input from
35
+ * @returns Paths to other config files to also include
36
+ */
14
37
export async function collectBeansFromConfigFile ( filePath : string ) : Promise < string [ ] > {
15
38
let json : string ;
16
39
try {
@@ -22,59 +45,145 @@ export async function collectBeansFromConfigFile(filePath: string): Promise<stri
22
45
for ( const qualifiedClassName in annotationConfig . components ) {
23
46
if ( ! annotationConfig . components . hasOwnProperty ( qualifiedClassName ) )
24
47
continue ;
25
- // Create bean ID
26
- // TODO: Handle name parameter to @Component constructor
48
+ // TODO: Handle name value on @Component annotation
27
49
const id = lowerInitialChar ( < string > qualifiedClassName . split ( "." ) . pop ( ) ) ;
28
- // Check for duplicate ids from other beans
29
- if ( beansById . has ( id ) )
30
- throw new ConfigParseError ( `Found multiple components with id '${ id } '` ) ;
31
- const aliasedBeanId = model . Bean . getAlias ( id ) ;
32
- if ( aliasedBeanId !== undefined )
33
- throw new ConfigParseError ( `Found a bean with id same as an alias for '${ aliasedBeanId } '` ) ;
34
- // Create a bean model for the bean
35
- const component = { qualifiedClassName : qualifiedClassName , component : annotationConfig . components [ qualifiedClassName ] } ;
36
- beansById . set ( id , component ) ;
37
- model . Bean . registerIdForClass ( component . qualifiedClassName , id ) ;
50
+ checkBeanId ( id , `@Component annotation on class '${ qualifiedClassName } '` ) ;
51
+ componentsById . set ( id , { qualifiedClassName : qualifiedClassName , component : annotationConfig . components [ qualifiedClassName ] } ) ;
52
+ model . Bean . registerIdForClass ( qualifiedClassName , id ) ;
53
+ }
54
+ for ( const configurationClassName in annotationConfig . configurations ) {
55
+ if ( ! annotationConfig . configurations . hasOwnProperty ( configurationClassName ) )
56
+ continue ;
57
+ const beanDefinitionMethods = annotationConfig . configurations [ configurationClassName ] . beanDefinitionMethods ;
58
+ for ( const beanDefinitionMethodName in beanDefinitionMethods ) {
59
+ if ( ! beanDefinitionMethods . hasOwnProperty ( beanDefinitionMethodName ) )
60
+ continue ;
61
+ const beanDefinitionMethod = beanDefinitionMethods [ beanDefinitionMethodName ] ;
62
+ let id = beanDefinitionMethodName ;
63
+ if ( beanDefinitionMethod . specifiedNames !== undefined && beanDefinitionMethod . specifiedNames . length !== 0 ) {
64
+ id = beanDefinitionMethod . specifiedNames [ 0 ] ;
65
+ // Add aliases
66
+ for ( let i = 1 ; i < beanDefinitionMethod . specifiedNames . length ; ++ i )
67
+ model . Bean . addAlias ( beanDefinitionMethod . specifiedNames [ i ] , id ) ;
68
+ }
69
+ checkBeanId ( id , `@Bean annotation on '${ configurationClassName } .${ beanDefinitionMethodName } '` ) ;
70
+ beansById . set ( id , { className : configurationClassName , name : configurationClassName , method : beanDefinitionMethod } ) ;
71
+ model . Bean . registerIdForClass ( beanDefinitionMethod . type , id ) ;
72
+ }
38
73
}
39
74
// TODO: Return paths to XML config files specified in code
40
75
return [ ] ;
41
76
}
42
77
78
+ /**
79
+ * Check for duplicate ids from other beans or aliases
80
+ * @param id The bean identifier to check
81
+ * @param location The location to include in the text of any thrown exception
82
+ * @throws [[ConfigParseError]] If the bean identifier has already been used
83
+ */
84
+ function checkBeanId ( id : string , location : string ) : void {
85
+ if ( componentsById . has ( id ) )
86
+ throw new ConfigParseError ( `${ location } : There is an existing component with id '${ id } '` ) ;
87
+ if ( beansById . has ( id ) )
88
+ throw new ConfigParseError ( `${ location } : There is an existing bean with id '${ id } '` ) ;
89
+ const aliasedBeanId = model . Bean . getAlias ( id ) ;
90
+ if ( aliasedBeanId !== undefined )
91
+ throw new ConfigParseError ( `${ location } : There is an existing alias with identifier '${ aliasedBeanId } '` ) ;
92
+ }
93
+
94
+ /**
95
+ * Check whether a bean identifier is already implemented by an element from the JSON configuration
96
+ * @param id The bean identifier to check
97
+ * @returns True if the bean identifier is already registered
98
+ */
43
99
export function beanWithIdExists ( id : string ) : boolean {
44
- return beansById . has ( id ) ;
100
+ return componentsById . has ( id ) || beansById . has ( id ) ;
45
101
}
46
102
103
+ /**
104
+ * Create the model for each bean discovered during the execution of [[collectBeansFromConfigFile]]
105
+ */
47
106
export function parseAllBeans ( ) : void {
48
- for ( const [ beanId , bean ] of beansById ) {
107
+ for ( const [ beanId , component ] of componentsById ) {
108
+ if ( model . Bean . tryGet ( beanId ) !== undefined )
109
+ continue ;
110
+ // We don't store the results of this parsing, it is stored in internal data structures which do enough for us
111
+ parseJsonComponent ( beanId , component ) ;
112
+ }
113
+ for ( const [ beanId , namedMethod ] of beansById ) {
49
114
if ( model . Bean . tryGet ( beanId ) !== undefined )
50
115
continue ;
51
116
// We don't store the results of this parsing, it is stored in internal data structures which do enough for us
52
- parseJsonBean ( beanId , bean ) ;
117
+ parseJsonBean ( beanId , namedMethod ) ;
53
118
}
54
119
}
55
120
121
+ /**
122
+ * Create the model for a bean with the given identifier
123
+ * @param id The identifier of a bean implemented by a component or bean definition method
124
+ * @returns The model for the bean
125
+ */
56
126
export function parseBean ( id : string ) : model . Bean {
127
+ const component = componentsById . get ( id ) ;
128
+ if ( component !== undefined )
129
+ return parseJsonComponent ( id , component ) ;
57
130
const bean = beansById . get ( id ) ;
58
- if ( bean = == undefined )
59
- throw new Error ( "Called parseBean on a bean that did not exist" ) ;
60
- return parseJsonBean ( id , bean ) ;
131
+ if ( bean ! == undefined )
132
+ return parseJsonBean ( id , bean ) ;
133
+ throw new Error ( "Called parseBean on a bean that did not exist" ) ;
61
134
}
62
135
63
- function parseJsonBean ( id : string | undefined , namedBean : NamedComponent ) : model . Bean {
136
+ /**
137
+ * Create the model for a bean with the given identifier defined by a component from the JSON configuration file
138
+ * @param id The identifier of a bean implemented by a component
139
+ * @param namedBean The component definition from the JSON configuration file
140
+ * @returns The model for the bean
141
+ */
142
+ function parseJsonComponent ( id : string , namedBean : NamedComponent ) : model . Bean {
64
143
const component = namedBean . component ;
65
144
const properties : model . BeanProperty [ ] = [ ] ;
66
145
for ( const fieldName in component . fields ) {
67
146
if ( ! component . fields . hasOwnProperty ( fieldName ) )
68
147
continue ;
69
- const fieldClass = component . fields [ fieldName ] . type ;
70
-
71
- const fieldBeanId = model . Bean . tryGetIdByClass ( fieldClass ) ;
72
- if ( fieldBeanId === undefined )
73
- throw new ConfigParseError (
74
- `Auto-wired field '${ namedBean . qualifiedClassName } .${ fieldName } ' depends on a class '${ fieldClass } ' `
75
- + `that has ${ model . Bean . hasMultipleBeansForClass ( fieldClass ) ? "multiple" : "no" } implementations` ) ;
76
- properties . push ( { name : fieldName , value : new model . BeanRefValue ( fieldBeanId ) } ) ;
148
+ properties . push ( {
149
+ name : fieldName ,
150
+ value : getBeanForClass ( component . fields [ fieldName ] . type , `Auto-wired field '${ namedBean . qualifiedClassName } .${ fieldName } '` ) ,
151
+ } ) ;
77
152
}
78
153
// TODO: Handle constructor arguments
79
154
return new model . Bean ( undefined , id , namedBean . qualifiedClassName , false , [ ] , properties ) ;
80
155
}
156
+
157
+ /**
158
+ * Create the model for a bean with the given identifier defined by a component from the JSON configuration file
159
+ * @param id The identifier of a bean implemented by a bean definition method
160
+ * @param namedMethod The bean definition method definition from the JSON configuration file
161
+ * @returns The model for the bean
162
+ */
163
+ function parseJsonBean ( id : string , namedMethod : NamedBeanDefinitionMethod ) : model . Bean {
164
+ const fieldBeanId = model . Bean . tryGetIdByClass ( namedMethod . className ) ;
165
+ if ( fieldBeanId === undefined )
166
+ throw new ConfigParseError (
167
+ `Factory method '${ namedMethod . name } ' is defined in a class '${ namedMethod . className } ' `
168
+ + `that has ${ model . Bean . hasMultipleBeansForClass ( namedMethod . className ) ? "multiple" : "no" } implementations` ) ;
169
+ const factoryBean = getBeanById ( fieldBeanId ) ;
170
+ if ( factoryBean === undefined )
171
+ throw new ConfigParseError (
172
+ `Couldn't find bean '${ namedMethod . className } ' that is configuration containing factory method '${ namedMethod . name } '` ) ;
173
+ const method = namedMethod . method ;
174
+ const argumentValues : model . Value [ ] = [ ] ;
175
+ for ( const parameterType of method . parameterTypes ) {
176
+ argumentValues . push ( getBeanForClass ( parameterType , `Parameter to factory method '${ namedMethod . className } .${ namedMethod . name } '` ) ) ;
177
+ }
178
+ const isLazyInit = method . scope !== undefined && method . scope !== "singleton" || method . isLazyInit === true ;
179
+ return new model . Bean ( undefined , id , { bean : factoryBean , method : namedMethod . name } , isLazyInit , argumentValues , [ ] ) ;
180
+ }
181
+
182
+ function getBeanForClass ( className : string , userString : string ) : model . BeanRefValue {
183
+ const fieldBeanId = model . Bean . tryGetIdByClass ( className ) ;
184
+ if ( fieldBeanId === undefined )
185
+ throw new ConfigParseError (
186
+ `${ userString } depends on a class '${ className } ' `
187
+ + `that has ${ model . Bean . hasMultipleBeansForClass ( className ) ? "multiple" : "no" } implementations` ) ;
188
+ return new model . BeanRefValue ( fieldBeanId ) ;
189
+ }
0 commit comments