forked from astav/JsonToJava
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGenerator.java
More file actions
296 lines (266 loc) · 15.3 KB
/
Copy pathGenerator.java
File metadata and controls
296 lines (266 loc) · 15.3 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
package com.astav.jsontojava;
import com.astav.jsontojava.classmanager.GeneratedClassManager;
import com.astav.jsontojava.classmanager.ImportClassManager;
import com.astav.jsontojava.regex.RegexFilter;
import com.astav.jsontojava.template.JavaTemplate;
import com.astav.jsontojava.util.MergeMapCollectionHelper;
import com.astav.jsontojava.util.MapValuesHelper;
import com.astav.jsontojava.util.StringHelper;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import java.util.Map;
/**
* User: Astav
* Date: 10/21/12
*/
public class Generator {
public static final String DEFAULT_TYPE = "Object";
public static final String ENTRY_POSTFIX_NAME = "Entry";
public static final String LIST_TYPE_GENERIC = "List<%s>";
public static final String MAP_TYPE_GENERIC = "Map<%s, %s>";
public static final String MAP_DEFAULT_KEY_TYPE = "String";
private final String packageName;
private final String outputDirectory;
private final ClassFiles classFiles = new ClassFiles();
private final MergeMapCollectionHelper mergeMapCollectionHelper = new MergeMapCollectionHelper();
private final MapValuesHelper mapValuesHelper = new MapValuesHelper();
private final ObjectMapper objectMapper = new ObjectMapper();
private final JavaTemplate javaTemplate = new JavaTemplate();
private final GeneratedClassManager generatedClassManager;
private final Optional<ImportClassManager> importClassManager;
private final Optional<RegexFilter> regexFilter;
private final boolean promptForComplexValueTypes;
private int generatedFileCount = 0;
public Generator(String outputDirectory, String packageName, String regexFilename, String importDirectory, List<String> importPackages, boolean promptForComplexValueTypes) throws IOException, ClassNotFoundException {
this.packageName = packageName;
System.out.println("Package name is '" + this.packageName + "'");
this.outputDirectory = outputDirectory;
System.out.println("Output directory is '" + this.outputDirectory + "'");
this.generatedClassManager = new GeneratedClassManager(outputDirectory, packageName);
this.promptForComplexValueTypes = promptForComplexValueTypes;
File regexFile = new File(regexFilename);
if (regexFile.exists()) {
System.out.println(String.format("Using regex file '%s'", regexFilename));
RegexFilter regexFilter = objectMapper.readValue(regexFile, new TypeReference<RegexFilter>() {
});
this.regexFilter = Optional.of(regexFilter);
} else {
System.out.println(String.format("Regex file not supplied or not found '%s'.", regexFilename));
this.regexFilter = Optional.absent();
}
if (importDirectory == null || importPackages == null) {
System.out.println("Not importing existing classes.");
this.importClassManager = Optional.absent();
} else {
System.out.println(String.format("Importing classes from '%s' with specified packages.", importDirectory));
this.importClassManager = Optional.of(new ImportClassManager(importDirectory, importPackages));
}
}
public void generateClasses(String key, Map<String, Object> classData) throws IOException, ClassNotFoundException {
String className = StringHelper.capFirstLetter(key);
ClassFileData classFileData = classFiles.get(className);
for (Map.Entry<String, Object> entry : classData.entrySet()) {
Object value = entry.getValue();
String entryKey = entry.getKey();
Class<?> aClass = value == null ? Object.class : value.getClass();
ClassMetaData vcMetaData = getClassMetaData(entryKey, value, aClass);
classFileData.getMapOfVariablesToTypes().put(vcMetaData.getVariableName(), vcMetaData.getVariableType());
classFileData.addImportPackages(vcMetaData.getImportPackages());
Optional<GenerateClassForMap> generateClassForMapOptional = vcMetaData.getGenerateClass();
if (generateClassForMapOptional.isPresent()) {
GenerateClassForMap generateClassForMap = generateClassForMapOptional.get();
//noinspection unchecked
generateClasses(generateClassForMap.getClassName(), generateClassForMap.getData());
}
}
File javaSourceFile = javaTemplate.writeOutJavaFile(className, outputDirectory, packageName, classFiles);
generatedFileCount++;
generatedClassManager.compileAndLoadClass(className, javaSourceFile, outputDirectory);
}
private ClassMetaData getClassMetaData(String entryKey, Object value, Class<?> aClass) throws IOException {
ClassMetaData vcMetaData = new ClassMetaData();
Optional<String> postfixClass = regexFilter.isPresent() ?
regexFilter.get().getTypeForKey(entryKey) : Optional.<String>absent();
if (postfixClass.isPresent()) { // match regex
String type = postfixClass.get();
System.out.println(String.format(" Using %s for json field '%s'", type, entryKey));
vcMetaData.setVariableType(StringHelper.capFirstLetter(type));
vcMetaData.setVariableName(entryKey);
return vcMetaData;
}
Optional<Class> isAPrimitiveClass = mapValuesHelper.getPrimitiveClass(aClass, value);
if (isAPrimitiveClass.isPresent()) { // primitive class match
Class<?> primitiveClass = isAPrimitiveClass.get();
vcMetaData.setVariableType(StringHelper.capFirstLetter(primitiveClass.getSimpleName()));
vcMetaData.setVariableName(entryKey);
return vcMetaData;
}
List<Class<?>> matchingClasses = importClassManager.isPresent() ?
importClassManager.get().getMatchingClasses(value) : Lists.<Class<?>>newArrayList();
matchingClasses.addAll(generatedClassManager.getMatchingClasses(value));
if (!matchingClasses.isEmpty()) { // one of the loaded classes works, use that instead..
int useIndex = askUserToPickOneIfMultipleClasses(entryKey, matchingClasses);
if (useIndex >= 0) {
Class<?> chosenClass = matchingClasses.get(useIndex);
System.out.println(String.format(" Using %s for json field '%s'", chosenClass.getName(), entryKey));
vcMetaData.setVariableType(StringHelper.capFirstLetter(chosenClass.getSimpleName()));
vcMetaData.setVariableName(entryKey);
String name = chosenClass.getPackage().getName();
if (!name.equals(packageName)) {
vcMetaData.addImportPackage(name + "." + chosenClass.getSimpleName());
}
return vcMetaData;
}
}
if (Map.class.isAssignableFrom(aClass)) { // map type match
boolean stayAsMap = false;
Map mapValue = (Map) value;
if (!mapValue.isEmpty()) {
Optional<Class<?>> valuesPrimitiveType = mapValuesHelper.areAllValuesTheSamePrimitiveType(mapValue);
Object aKey = mapValue.keySet().iterator().next();
Optional<Class> mapKeyPrimitiveClass = mapValuesHelper.getPrimitiveClass(aKey.getClass(), aKey);
boolean mapKeyIsANumber = Number.class.isAssignableFrom(mapKeyPrimitiveClass.get());
if (mapKeyIsANumber) { // if the key is a number stay as a map
stayAsMap = true;
} else if (valuesPrimitiveType.isPresent()) { // if all values are the same primitive type
stayAsMap = !askUserIfNewClassIsRequired(
entryKey,
mapValue,
valuesPrimitiveType.get().getSimpleName(),
String.format("called '%s'", StringHelper.capFirstLetter(entryKey)),
true);
} else if(promptForComplexValueTypes
&& mapValuesHelper.areAllValuesComplexTypes(mapValue)) { // if all values are complex types (assignable to a map)
stayAsMap = !askUserIfNewClassIsRequired(
entryKey,
mapValue,
StringHelper.capFirstLetter(getEntryKeyWithPostfix(entryKey)),
"for every key in the map",
false);
}
if (!stayAsMap) {
vcMetaData.setVariableType(StringHelper.capFirstLetter(entryKey));
vcMetaData.setVariableName(entryKey);
vcMetaData.setGenerateClass(Optional.of(new GenerateClassForMap(vcMetaData.getVariableType(), mapValue)));
} else {
if (!valuesPrimitiveType.isPresent()) {
@SuppressWarnings("unchecked") Map<String, Object> allEntryMap = mergeMapCollectionHelper.merge(mapValue.values());
String entryName = getEntryKeyWithPostfix(entryKey);
vcMetaData.setVariableType(String.format(MAP_TYPE_GENERIC, MAP_DEFAULT_KEY_TYPE, StringHelper.capFirstLetter(entryName)));
vcMetaData.setVariableName(entryKey);
vcMetaData.setGenerateClass(Optional.of(new GenerateClassForMap(entryName, allEntryMap)));
} else {
@SuppressWarnings("SuspiciousMethodCalls") Object aValue = mapValue.get(aKey);
ClassMetaData mapValueMetaData = getClassMetaData(entryKey, aValue, aValue.getClass());
vcMetaData.setVariableType(String.format(MAP_TYPE_GENERIC, MAP_DEFAULT_KEY_TYPE, mapValueMetaData.getVariableType()));
vcMetaData.setVariableName(entryKey);
vcMetaData.setGenerateClass(mapValueMetaData.getGenerateClass());
}
}
} else {
String defaultType = String.format(MAP_TYPE_GENERIC, MAP_DEFAULT_KEY_TYPE, DEFAULT_TYPE);
printWarnDefaultType(entryKey, defaultType);
vcMetaData.setVariableType(defaultType);
vcMetaData.setVariableName(entryKey);
}
} else if (List.class.isAssignableFrom(aClass)) { // list type match
List listValue = (List) value;
if (listValue.size() > 0) {
Object listEntryValue = listValue.get(0); // get the first entry?
ClassMetaData listEntryMetaData = getClassMetaData(entryKey, listEntryValue, listEntryValue.getClass());
if (listEntryMetaData.getGenerateClass().isPresent()) {
@SuppressWarnings("unchecked") List<Map<String, Object>> collectionMap = (List<Map<String, Object>>) listValue;
Map<String, Object> allEntryMap = mergeMapCollectionHelper.merge(collectionMap);
String entryName = getEntryKeyWithPostfix(entryKey);
vcMetaData.setVariableType(String.format(LIST_TYPE_GENERIC, StringHelper.capFirstLetter(entryName)));
vcMetaData.setVariableName(entryKey);
vcMetaData.setGenerateClass(Optional.of(new GenerateClassForMap(entryName, allEntryMap)));
} else {
vcMetaData.setVariableType(String.format(LIST_TYPE_GENERIC, listEntryMetaData.getVariableType()));
vcMetaData.setVariableName(entryKey);
vcMetaData.setGenerateClass(listEntryMetaData.getGenerateClass());
}
} else {
String defaultType = String.format(LIST_TYPE_GENERIC, DEFAULT_TYPE);
printWarnDefaultType(entryKey, defaultType);
vcMetaData.setVariableType(defaultType);
vcMetaData.setVariableName(entryKey);
}
} else {
printWarnDefaultType(entryKey, DEFAULT_TYPE);
vcMetaData.setVariableType(DEFAULT_TYPE);
vcMetaData.setVariableName(entryKey);
}
return vcMetaData;
}
private String getEntryKeyWithPostfix(String entryKey) {
return entryKey + ENTRY_POSTFIX_NAME;
}
private void printWarnDefaultType(String entryKey, String defaultType) {
System.out.println(String.format(" *Warning*: Chosen type '%s' for json field '%s' with null value", defaultType, entryKey));
}
private int askUserToPickOneIfMultipleClasses(String entryKey, List<Class<?>> matchingClasses) throws IOException {
int useIndex;
int defaultIndex = matchingClasses.size() == 1 ? 2 : 1;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println();
System.out.println(String.format("What would you like to use for '%s'...", entryKey));
System.out.println(String.format(" 1. Generate a new class '%s'", StringHelper.capFirstLetter(entryKey)));
int i = 2;
for (Class<?> matchingClass : matchingClasses) {
System.out.println(" " + i++ + ". " + matchingClass.getSimpleName() + " in package (" + matchingClass.getPackage().getName() + ")");
}
Integer valueChosen = null;
while (valueChosen == null) {
System.out.print(String.format("Pick an option for json field '%s' [default: %s]: ", entryKey, defaultIndex));
String inputStr = br.readLine();
if (inputStr.isEmpty()) {
valueChosen = defaultIndex;
} else {
valueChosen = Integer.valueOf(inputStr);
}
if (valueChosen < 1 || (valueChosen - 1) > matchingClasses.size()) valueChosen = null;
}
System.out.println();
switch (valueChosen) {
case 1:
useIndex = -1;
break;
default:
useIndex = valueChosen - 2;
break;
}
return useIndex;
}
private boolean askUserIfNewClassIsRequired(String entryKey, Map mapValue, String forMapValue, String secondOption, boolean defaultIsMap) throws IOException {
System.out.println();
int defaultEntryIndex = defaultIsMap ? 1 : 2;
System.out.println(String.format("What would you like to use for json field '%s'\n%s\n...", entryKey, objectMapper.writeValueAsString(mapValue)));
System.out.println(String.format(" 1. Use a 'Map<String, %s>'", forMapValue));
System.out.println(String.format(" 2. Generate a new class %s", secondOption));
Integer valueChosen = null;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while (valueChosen == null
|| (valueChosen != 1
&& valueChosen != 2)) {
System.out.print(String.format("Pick a class for json field '%s' [default: %s]: ", entryKey, defaultEntryIndex));
String inputStr = br.readLine();
if (inputStr.isEmpty()) {
valueChosen = defaultEntryIndex;
} else {
valueChosen = Integer.valueOf(inputStr);
}
}
return valueChosen == 2;
}
public int getGeneratedFileCount() {
return generatedFileCount;
}
}