forked from Ahmed-Ali/JSONExport
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFileContentBuilder.swift
More file actions
executable file
·373 lines (301 loc) · 13.8 KB
/
Copy pathFileContentBuilder.swift
File metadata and controls
executable file
·373 lines (301 loc) · 13.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
//
// FileGenerator.swift
// JSONExport
//
// Created by Ahmed on 11/14/14.
// Copyright (c) 2014 Ahmed Ali. All rights reserved.
//
import Foundation
/**
Singleton used to build the file content with the current configurations
*/
class FilesContentBuilder{
private init() {}
/**
Lazely load and return the singleton instance of the FilesContentBuilder
*/
struct Static {
static var onceToken : Int = 0
static var instance : FilesContentBuilder? = nil
}
class var instance : FilesContentBuilder {
_ = FilesContentBuilder.__once
return Static.instance!
}
private static var __once: () = {
Static.instance = FilesContentBuilder()
}()
/**
The prefix used for first level type names (and file names as well)
*/
var classPrefix = ""
/**
The language for which the files' content should be created
*/
var lang : LangModel!
/**
Whether to include utility methods in the generated content
*/
var includeUtilities = true
/**
Whether to include constructors (aka initializers)
*/
var includeConstructors = true
/**
Whatever value will be assinged to the firstLine property, will appear as the first line in every file if the target language supports first line statement
*/
var firstLine = ""
/**
If the target language supports inheritance, all the generated classes will be subclasses of this class
*/
var parentClassName = ""
var mismatchedTypes = [String : String]()
/**
Generates a file using the passed className and jsonObject example and appends it in the passed files array
- parameter className: for the file to be generated, if the file already exist with different name, this className will be changed
- parameter jsonObject: acts as an example of the json object, which the generated fill be able to handle
- parameter files: the generated file will be appended to this array
*/
func addFileWithName(_ className: inout String, jsonObject: NSDictionary, files : inout [FileRepresenter], toOneRelationWithProperty: Property! = nil)
{
var properties = [Property]()
if !className.hasPrefix(classPrefix){
className = "\(classPrefix)\(className)"
}
if toOneRelationWithProperty != nil && lang.supportMutualRelationships != nil && lang.supportMutualRelationships!{
properties.append(toOneRelationWithProperty)
}
//sort all the keys in the passed json dictionary
let jsonProperties = (jsonObject.allKeys as! [String]).sorted()
//loop all the json properties and handle each one individually
for jsonPropertyName in jsonProperties{
let value : AnyObject = jsonObject[jsonPropertyName]! as AnyObject
let property = propertyForValue(value, jsonKeyName: jsonPropertyName)
//Avoid duplicated property names
if properties.map({$0.nativeName}).index(of: property.nativeName) != nil{
continue
}
//recursively handle custom types
if property.isCustomClass{
let rProperty = relationProperty(className)
addFileWithName(&property.type, jsonObject: value as! NSDictionary, files:&files, toOneRelationWithProperty: rProperty)
}else if property.isArray{
let array = value as! NSArray
if array.firstObject is NSDictionary{
//complicated enough.....
var type = property.elementsType
let relatedProperty = relationProperty(className)
let allProperties = unionDictionaryFromArrayElements(array);
addFileWithName(&type, jsonObject: allProperties, files:&files, toOneRelationWithProperty: relatedProperty)
}
}
properties.append(property)
}
//create the file
let file = FileRepresenter(className: className, properties: properties, lang:lang)
file.includeUtilities = includeUtilities
file.includeConstructors = includeConstructors
file.firstLine = firstLine
file.parentClassName = parentClassName
var exactMatchFound = false
if let similarFile = findSimilarFile(file, inFiles: files, exactMatchFound: &exactMatchFound){
//there is a similar file
if !exactMatchFound{
//If the found file is no exact, then any additional properties to the alread exist file instead of creating new one
mergeProperties(fromFile: file, toFile: similarFile)
}
//Hold list of mismatches to be fixed later
mismatchedTypes[className] = similarFile.className
className = similarFile.className
}else{
files.append(file)
if lang.headerFileData != nil{
//add header file first
let headerFile = HeaderFileRepresenter(className: className, properties: properties, lang:lang)
headerFile.includeUtilities = includeUtilities
headerFile.includeConstructors = includeConstructors
headerFile.parentClassName = parentClassName
headerFile.firstLine = firstLine
files.append(headerFile)
}
}
}
/**
Merges the properties from the passed fromFile to the pass toFile
- parameter fromFile: in which to find any new properties
- parameter toFile: to which to add any found new properties
*/
func mergeProperties(fromFile: FileRepresenter, toFile: FileRepresenter)
{
for property in fromFile.properties{
if toFile.properties.index(of: property) == nil{
toFile.properties.append(property)
}
}
}
/**
Finds the first file in the passed files which has the same class name as the passed file
- parameter file: the file to compare against
- parameter inFiles: the files array to search in
- parameter exactMathFound: inout param, will have the value of 'true' if any file is found that has exactly the same properties as the passed file
- returns: similar file if any
*/
func findSimilarFile(_ file: FileRepresenter, inFiles files: [FileRepresenter], exactMatchFound: inout Bool) -> FileRepresenter?{
var similarFile : FileRepresenter?
for targetFile in files{
exactMatchFound = bothFilesHasSamePropreties(file1: targetFile, file2: file)
if exactMatchFound || targetFile.className == file.className{
similarFile = targetFile
break
}
}
return similarFile
}
/**
Compares the properties of both files to determine if they exactly similar or no.
- parameter file1: first file to compare against the second file
- parameter file2: the second file to compare against the first file
- returns: whether both files has exactly the same properties
*/
func bothFilesHasSamePropreties(file1: FileRepresenter, file2: FileRepresenter) -> Bool
{
var bothHasSameProperties = true
if file1.properties.count == file2.properties.count{
//there is a propability they both has the same properties
for property in file1.properties{
if file2.properties.index(of: property) == nil{
//property not found, no need to keep looking
bothHasSameProperties = false
break
}
}
}else{
bothHasSameProperties = false
}
return bothHasSameProperties
}
func fixReferenceMismatches(inFiles files: [FileRepresenter])
{
for file in files{
for property in file.properties{
if property.isCustomClass, let toType = mismatchedTypes[property.type]{
property.type = toType
}else if property.isArray, let toType = mismatchedTypes[property.elementsType]{
property.elementsType = toType
property.type = lang.arrayType.replacingOccurrences(of: elementType, with: toType)
}
}
}
}
/**
Creates and returns a Property object whiche represents a to-one relation property
- parameter relationClassName: to which the relation relates
- parameter headerProperty: optional whether this property is for header file
- returns: the relation property
*/
func relationProperty(_ relationClassName : String) -> Property
{
let nativeName = relationClassName.lowercaseFirstChar()
let property = Property(jsonName: nativeName, nativeName: nativeName, type: relationClassName, lang: lang)
property.isCustomClass = true
return property
}
/**
Creates and returns a Property object passed on the passed value and json key name
- parameter value: example value for the property
- parameter jsonKeyName: for the property
- returns: a Property instance
*/
func propertyForValue(_ value: AnyObject, jsonKeyName: String) -> Property
{
let nativePropertyName = propertyNativeName(jsonKeyName)
var type = propertyTypeName(value, lang:lang)
// var isDictionary = false
// var isArray = false
var property: Property!
if value is NSDictionary {
type = typeNameForPropertyName(jsonKeyName)
property = Property(jsonName: jsonKeyName, nativeName: nativePropertyName, type: type, isArray:false, isCustomClass: true, lang: lang)
}else if value is NSArray{
//we need to check its elements...
let array = value as! NSArray
if array.firstObject is NSDictionary{
//wow complicated
let leafClassName = typeNameForPropertyName(jsonKeyName)
type = lang.arrayType.replacingOccurrences(of: elementType, with: leafClassName)
property = Property(jsonName: jsonKeyName, nativeName: nativePropertyName, type: type, isArray: true, isCustomClass: false, lang:lang)
property.elementsType = leafClassName
//Create a class for this type as well!
property.elementsAreOfCustomType = true
}else{
property = Property(jsonName: jsonKeyName, nativeName: nativePropertyName, type: type, isArray: true, isCustomClass: false, lang:lang)
property.elementsType = typeNameForArrayElements(value as! NSArray, lang:lang)
}
}else{
property = Property(jsonName: jsonKeyName, nativeName: nativePropertyName, type: type, lang:lang)
}
property.sampleValue = value
return property
}
/**
Returns a camel case presentation from the passed json key
- parameter jsonKeyName: the name of the json key to convert to suitable native property name
- returns: property name
*/
func propertyNativeName(_ jsonKeyName : String) -> String
{
var propertyName = cleanUpVersionOfPropertyNamed(jsonKeyName)
propertyName = underscoresToCamelCaseForString(propertyName, startFromFirstChar: false).lowercaseFirstChar()
//Fix property name that could be a reserved keyword
if lang.reservedKeywords != nil && lang.reservedKeywords.contains(propertyName.lowercased()){
//Property name need to be suffixed by proper suffix, any ideas of better generlized prefix/suffix?
propertyName += "Field"
}
return propertyName
}
func cleanUpVersionOfPropertyNamed(_ propertyName: String) -> String
{
let allowedCharacters = NSMutableCharacterSet.alphanumeric()
allowedCharacters.addCharacters(in: "_1234567890")
let cleanVersion = propertyName.components(separatedBy: allowedCharacters.inverted).joined(separator: "")
return cleanVersion
}
/**
Returns the input string with white spaces removed, and underscors converted to camel case
- parameter inputString: to convert
- parameter startFromFirstChar: whether to start with upper case letter
- returns: the camel case version of the input
*/
func underscoresToCamelCaseForString(_ input: String, startFromFirstChar: Bool) -> String
{
var str = input.replacingOccurrences(of: " ", with: "")
str = str.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
var output = ""
var makeNextCharUpperCase = startFromFirstChar
for char in input.characters{
if char == "_" {
makeNextCharUpperCase = true
}else if makeNextCharUpperCase{
let upperChar = String(char).uppercased()
output += upperChar
makeNextCharUpperCase = false
}else{
makeNextCharUpperCase = false
output += String(char)
}
}
return output
}
/**
Creats and returns the class name for the passed proeprty name
- parameter propertyName: to be converted to a type name
- returns: the type name
*/
func typeNameForPropertyName(_ propertyName : String) -> String{
var swiftClassName = underscoresToCamelCaseForString(propertyName, startFromFirstChar: true).toSingular()
if !swiftClassName.hasPrefix(classPrefix){
swiftClassName = "\(classPrefix)\(swiftClassName)"
}
return swiftClassName
}
}