├── .gitignore
├── LICENSE.txt
├── README.cn.md
├── README.md
├── build.gradle
├── image
├── config.jpg
├── config_zh.jpg
├── input.jpg
├── input_zh.jpg
└── menu.jpg
├── libs
└── lombok.jar
├── settings.gradle
└── src
├── main
├── java
│ ├── liwey
│ │ └── json2pojo
│ │ │ ├── Config.java
│ │ │ ├── ConfigUtil.java
│ │ │ ├── GenerateAction.java
│ │ │ ├── Generator.java
│ │ │ ├── GeneratorDialog.form
│ │ │ ├── GeneratorDialog.java
│ │ │ ├── R.java
│ │ │ ├── SettingsDialog.form
│ │ │ └── SettingsDialog.java
│ └── org
│ │ └── jboss
│ │ └── dna
│ │ └── common
│ │ └── text
│ │ └── Inflector.java
└── resources
│ ├── META-INF
│ ├── plugin.xml
│ └── plugin_zh.xml
│ ├── bundles_en.properties
│ ├── bundles_zh.properties
│ └── icons
│ └── json.png
└── test
└── java
├── example.json
└── liwey
└── json2pojo
└── GeneratorTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # User-specific stuff:
2 | .idea/
3 | .gradle/
4 |
5 | # Binaries
6 | out/
7 | build/
8 | src/test/java/example/
9 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright 2016 Hexar W. Anderson
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/README.cn.md:
--------------------------------------------------------------------------------
1 | [English README](README.md)
2 |
3 | ## 简介
4 | IntelliJ Idea插件,从JSON文本生成POJO, 并添加Lombok与Gson/Jackson注解.
5 |
6 | ## 安装
7 | 从plugin库marketplace搜索`json2pojo with Lombok`。
8 |
9 | ## 使用
10 | 1. 右键目标package,选择"New-> Convert JSON to POJOs"
11 |
12 | 
13 |
14 | 2. 输入类名和源JSON文本。
15 |
16 | 
17 |
18 | ## 配置
19 | 
20 |
21 | 配置文件~/.json2pojo
22 |
23 | {
24 | "field.type.primitive": true,
25 | "field.name.annotation": 1,
26 | "lombok.accessors.fluent": false,
27 | "lombok.accessors.chain": false,
28 | "lombok.accessors.prefix": "",
29 | "lombok.builder": false,
30 | "lombok.data": true,
31 | "lombok.no.args.constructor": false,
32 | "lombok.required.args.constructor": true,
33 | "lombok.all.args.constructor": false,
34 | "suppress.warnings": "unused",
35 | "language": "en",
36 | "window.width": 512,
37 | "window.height": 400,
38 | "window.x": 648,
39 | "window.y": 252
40 | }
41 |
42 |
43 | ## Example
44 |
45 | 运行 `GeneratorTest`,生成的主类:
46 |
47 | package example.spark;
48 |
49 | import java.util.List;
50 | import com.google.gson.annotations.SerializedName;
51 | import lombok.Data;
52 | import lombok.RequiredArgsConstructor;
53 |
54 | @RequiredArgsConstructor
55 | @Data
56 | @SuppressWarnings("unused")
57 | public class SparkProgress {
58 | @SerializedName("batch.id")
59 | private long batchId;
60 | private DurationMs durationMs;
61 | private String id;
62 | @SerializedName("input-rows-per-second")
63 | private double inputRowsPerSecond;
64 | private String name;
65 | @SerializedName("num_input_rows")
66 | private long numInputRows;
67 | private double processedRowsPerSecond;
68 | private String runId;
69 | private Sink sink;
70 | private List sources;
71 | private List stateOperators;
72 | private String timestamp;
73 | }
74 |
75 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [中文 README](README.cn.md)
2 |
3 | ## Introduction
4 | An IntelliJ Idea plugin which generate POJOs from JSON with Lombok and gson/jackson annotations.
5 |
6 | ## Installation
7 | From IntelliJ Idea plugin marketplace search `json2pojo with Lombok`.
8 |
9 | ## Usage
10 | 1. From context of a package,select "New-> Generate POJOs from JSON".
11 |
12 | 
13 |
14 | 2. Input root class name and source JSON.
15 |
16 | 
17 |
18 | ## Settings
19 | 
20 |
21 | Settings file ~/.json2pojo
22 |
23 | {
24 | "primitive": true,
25 | "field.name.annotation": 1,
26 | "lombok.accessors": false,
27 | "lombok.accessors.fluent": true,
28 | "lombok.accessors.chain": true,
29 | "lombok.accessors.prefix": "",
30 | "lombok.builder": false,
31 | "lombok.data": true,
32 | "lombok.no.args.constructor": false,
33 | "lombok.required.args.constructor": true,
34 | "lombok.all.args.constructor": false
35 | }
36 |
37 | ## Example
38 |
39 | Run `GeneratorTest`, generated root class:
40 |
41 | package example.spark;
42 |
43 | import java.util.List;
44 | import com.google.gson.annotations.SerializedName;
45 | import lombok.Data;
46 | import lombok.RequiredArgsConstructor;
47 |
48 | @RequiredArgsConstructor
49 | @Data
50 | @SuppressWarnings("unused")
51 | public class SparkProgress {
52 | @SerializedName("batch.id")
53 | private long batchId;
54 | private DurationMs durationMs;
55 | private String id;
56 | @SerializedName("input-rows-per-second")
57 | private double inputRowsPerSecond;
58 | private String name;
59 | @SerializedName("num_input_rows")
60 | private long numInputRows;
61 | private double processedRowsPerSecond;
62 | private String runId;
63 | private Sink sink;
64 | private List sources;
65 | private List stateOperators;
66 | private String timestamp;
67 | }
68 |
69 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'org.jetbrains.intellij' version '0.4.1'
3 | id 'java'
4 | id 'idea'
5 | }
6 |
7 | group 'liwey'
8 | version '1.1'
9 |
10 | repositories {
11 | mavenCentral()
12 | }
13 |
14 | dependencies {
15 | compile files("libs/lombok.jar")
16 | compile "com.sun.codemodel:codemodel:2.6"
17 | compileOnly "org.projectlombok:lombok:1.18.4"
18 | }
19 |
20 | intellij {
21 | version '2018.3'
22 | }
23 |
24 | patchPluginXml {
25 | sinceBuild '162.00'
26 | untilBuild '201.00'
27 | }
--------------------------------------------------------------------------------
/image/config.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zenglian/json2pojo/75ba8699add62a40387a1fb69a88d1f8872d3c00/image/config.jpg
--------------------------------------------------------------------------------
/image/config_zh.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zenglian/json2pojo/75ba8699add62a40387a1fb69a88d1f8872d3c00/image/config_zh.jpg
--------------------------------------------------------------------------------
/image/input.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zenglian/json2pojo/75ba8699add62a40387a1fb69a88d1f8872d3c00/image/input.jpg
--------------------------------------------------------------------------------
/image/input_zh.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zenglian/json2pojo/75ba8699add62a40387a1fb69a88d1f8872d3c00/image/input_zh.jpg
--------------------------------------------------------------------------------
/image/menu.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zenglian/json2pojo/75ba8699add62a40387a1fb69a88d1f8872d3c00/image/menu.jpg
--------------------------------------------------------------------------------
/libs/lombok.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zenglian/json2pojo/75ba8699add62a40387a1fb69a88d1f8872d3c00/libs/lombok.jar
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'json2pojo'
2 |
3 |
--------------------------------------------------------------------------------
/src/main/java/liwey/json2pojo/Config.java:
--------------------------------------------------------------------------------
1 | package liwey.json2pojo;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * @author Leon Zeng
7 | * @since 2018/12/28 18:04
8 | */
9 | @Data
10 | public class Config {
11 | private boolean fieldTypePrimitive = false;
12 | private int fieldNameAnnotation = 0;
13 |
14 | private boolean lombokAccessorsFluent = false;
15 | private boolean lombokAccessorsChain = false;
16 | private String lombokAccessorsPrefix = "";
17 | private boolean lombokBuilder = false;
18 | private boolean lombokData = true;
19 | private boolean lombokNoArgsConstructor = false;
20 | private boolean lombokRequiredArgsConstructor = true;
21 | private boolean lombokAllArgsConstructor = false;
22 | private String suppressWarnings = "unused";
23 |
24 | private String language = "";
25 | private int windowWidth = 500;
26 | private int windowHeight = 450;
27 | private int windowX = 100;
28 | private int windowY = 100;
29 |
30 | public boolean useAccessors() {
31 | return isLombokAccessorsFluent() || isLombokAccessorsChain()
32 | || !lombokAccessorsPrefix.trim().isEmpty();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/liwey/json2pojo/ConfigUtil.java:
--------------------------------------------------------------------------------
1 | package liwey.json2pojo;
2 |
3 | import java.io.IOException;
4 | import java.nio.charset.Charset;
5 | import java.nio.file.Files;
6 | import java.nio.file.Path;
7 | import java.nio.file.Paths;
8 | import java.util.Locale;
9 |
10 | import com.google.gson.Gson;
11 | import com.google.gson.GsonBuilder;
12 |
13 | /**
14 | * @author Leon Zeng
15 | * @since 2018/12/29 13:56
16 | */
17 | public class ConfigUtil {
18 | private transient static final Path configPath = Paths.get(System.getProperty("user.home") + "/.json2pojo");
19 | private transient static final Gson gson = new GsonBuilder().setPrettyPrinting()
20 | .setFieldNamingStrategy(field -> separateCamelCase(field.getName(), ".").toLowerCase(Locale.ENGLISH))
21 | .create();
22 |
23 | public static final Config config = load();
24 |
25 | private static Config load() {
26 | try {
27 | return gson.fromJson(new String(Files.readAllBytes(configPath)), Config.class);
28 | } catch (Exception e) {
29 | return new Config();
30 | }
31 | }
32 |
33 | public static void save() throws IOException {
34 | Files.write(configPath, gson.toJson(config).getBytes());
35 | }
36 |
37 | /**
38 | * @see com.google.gson.FieldNamingPolicy
39 | */
40 | private static String separateCamelCase(String name, String separator) {
41 | StringBuilder translation = new StringBuilder();
42 | int i = 0;
43 |
44 | for (int length = name.length(); i < length; ++i) {
45 | char character = name.charAt(i);
46 | if (Character.isUpperCase(character) && translation.length() != 0) {
47 | translation.append(separator);
48 | }
49 |
50 | translation.append(character);
51 | }
52 |
53 | return translation.toString();
54 | }
55 |
56 | public static void setLocale() {
57 | String language = ConfigUtil.config.getLanguage().trim();
58 | if (language.startsWith("zh")) {
59 | Locale.setDefault(Locale.CHINESE);
60 | } else if (language.startsWith("en")) {
61 | Locale.setDefault(Locale.ENGLISH);
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/liwey/json2pojo/GenerateAction.java:
--------------------------------------------------------------------------------
1 | package liwey.json2pojo;
2 |
3 | import static liwey.json2pojo.ConfigUtil.config;
4 |
5 | import com.intellij.openapi.actionSystem.AnAction;
6 | import com.intellij.openapi.actionSystem.AnActionEvent;
7 | import com.intellij.openapi.actionSystem.LangDataKeys;
8 | import com.intellij.openapi.project.Project;
9 | import com.intellij.openapi.roots.ProjectRootManager;
10 | import com.intellij.openapi.vfs.VirtualFile;
11 |
12 | /**
13 | * A custom IntelliJ action which loads a dialog which will generate Java POJO classes from a given JSON text.
14 | */
15 | public class GenerateAction extends AnAction {
16 | @Override
17 | public void actionPerformed(AnActionEvent event) {
18 | ConfigUtil.setLocale();
19 |
20 | // Get the action folder
21 | Project project = event.getProject();
22 | VirtualFile actionFolder = event.getData(LangDataKeys.VIRTUAL_FILE);
23 |
24 | if (project != null && actionFolder != null && actionFolder.isDirectory()) {
25 | // Get the module source root and effective package name
26 | VirtualFile moduleSourceRoot = ProjectRootManager.getInstance(project).getFileIndex().getSourceRootForFile(actionFolder);
27 | String packageName = ProjectRootManager.getInstance(project).getFileIndex().getPackageNameByDirectory(actionFolder);
28 |
29 | // Show JSON dialog
30 | GeneratorDialog dialog = new GeneratorDialog(packageName, moduleSourceRoot.getPath());
31 | dialog.setVisible(true);
32 | }
33 | }
34 |
35 | @Override
36 | public void update(AnActionEvent event) {
37 | // Get the project and action folder
38 | Project project = event.getProject();
39 | VirtualFile actionFolder = event.getData(LangDataKeys.VIRTUAL_FILE);
40 |
41 | if (project != null && actionFolder != null && actionFolder.isDirectory()) {
42 | // Set visibility based on if the package name is non-null
43 | String packageName = ProjectRootManager.getInstance(project).getFileIndex().getPackageNameByDirectory(actionFolder);
44 | event.getPresentation().setVisible(packageName != null);
45 | } else {
46 | event.getPresentation().setVisible(false);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/liwey/json2pojo/Generator.java:
--------------------------------------------------------------------------------
1 | package liwey.json2pojo;
2 |
3 | import static liwey.json2pojo.ConfigUtil.config;
4 |
5 | import com.fasterxml.jackson.annotation.JsonProperty;
6 | import com.google.gson.JsonArray;
7 | import com.google.gson.JsonElement;
8 | import com.google.gson.JsonObject;
9 | import com.google.gson.JsonParser;
10 | import com.google.gson.JsonPrimitive;
11 | import com.google.gson.annotations.SerializedName;
12 | import com.sun.codemodel.JAnnotationArrayMember;
13 | import com.sun.codemodel.JAnnotationUse;
14 | import com.sun.codemodel.JBlock;
15 | import com.sun.codemodel.JCodeModel;
16 | import com.sun.codemodel.JDefinedClass;
17 | import com.sun.codemodel.JExpr;
18 | import com.sun.codemodel.JFieldVar;
19 | import com.sun.codemodel.JMethod;
20 | import com.sun.codemodel.JMod;
21 | import com.sun.codemodel.JPackage;
22 | import com.sun.codemodel.JType;
23 | import com.sun.codemodel.JVar;
24 | import java.io.File;
25 | import java.util.ArrayList;
26 | import java.util.Arrays;
27 | import java.util.Comparator;
28 | import java.util.HashMap;
29 | import java.util.Iterator;
30 | import java.util.List;
31 | import java.util.Map;
32 | import java.util.Set;
33 | import java.util.TreeSet;
34 | import java.util.stream.Collectors;
35 | import javax.swing.JTextField;
36 | import javax.swing.SwingUtilities;
37 | import lombok.AllArgsConstructor;
38 | import lombok.Builder;
39 | import lombok.Data;
40 | import lombok.NoArgsConstructor;
41 | import lombok.RequiredArgsConstructor;
42 | import lombok.experimental.Accessors;
43 | import org.apache.commons.lang.StringUtils;
44 | import org.jboss.dna.common.text.Inflector;
45 |
46 | /**
47 | * Contains the code to generate Java POJO classes from given JSON text.
48 | */
49 | class Generator {
50 | private static final JsonParser jsonParser = new JsonParser();
51 |
52 | private final String moduleSourceRoot;
53 | private final String packageName;
54 | private final JTextField resultTextField;
55 |
56 | private Map definedClasses = new HashMap<>();
57 | private JType deferredClass;
58 | private JType deferredList;
59 | private FieldComparator fieldComparator;
60 | private Map> fieldMap = new HashMap<>();
61 |
62 | Generator(final String packageName, final String moduleSourceRoot, final JTextField
63 | resultTextField) {
64 | this.moduleSourceRoot = moduleSourceRoot;
65 | this.packageName = packageName;
66 | this.resultTextField = resultTextField;
67 | }
68 |
69 | /**
70 | * Generates POJOs from source JSON text.
71 | *
72 | * @param rootName the name of the root class to generate.
73 | * @param json the source JSON text.
74 | */
75 | int generateFromJson(String rootName, String json) throws Exception {
76 | fieldComparator = new FieldComparator();
77 |
78 | // Create code model and package
79 | JCodeModel codeModel = new JCodeModel();
80 | JPackage codeModelPackage = codeModel._package(packageName);
81 |
82 | // Create deferrable types
83 | deferredClass = codeModel.ref(Deferred.class);
84 | deferredList = codeModel.ref(List.class).narrow(Deferred.class);
85 |
86 | // Parse the JSON data
87 | JsonElement rootNode = jsonParser.parse(json);
88 |
89 | // Recursively generate
90 | int classCount = generate(rootNode.getAsJsonObject(), formatClassName(rootName), codeModelPackage);
91 |
92 | // Build
93 | codeModel.build(new File(moduleSourceRoot));
94 | return classCount;
95 | }
96 |
97 | /**
98 | * Generates all of the sub-objects and fields for a given class.
99 | *
100 | * @param rootNode the JSON class node in the JSON syntax tree.
101 | * @param rootName the name of the root class to generate.
102 | * @param codeModelPackage the code model package to generate the class in.
103 | * @throws Exception if an error occurs.
104 | */
105 | private int generate(final JsonObject rootNode, final String rootName, final JPackage codeModelPackage) throws Exception {
106 | // First create all referenced sub-types and collect field data
107 | parseObject(rootNode, rootName, codeModelPackage);
108 |
109 | // Now create the actual fields
110 | for (JDefinedClass clazz : definedClasses.values()) {
111 | // Generate the fields
112 | SwingUtilities.invokeLater(() -> {
113 | if (resultTextField != null) {
114 | resultTextField.setText(R.get("generating", clazz.name()));
115 | }
116 | });
117 | List fields = generateFields(clazz, fieldMap.get(clazz), codeModelPackage.owner());
118 | }
119 | return definedClasses.size();
120 | }
121 |
122 | /**
123 | * Generates all of the sub-objects for a given class.
124 | *
125 | * @param classNode the JSON object node in the JSON syntax tree.
126 | * @param className the name of the class to create for this node.
127 | * @param codeModelPackage the code model package to generate the class in.
128 | * @throws Exception if an error occurs.
129 | */
130 | private void parseObject(final JsonObject classNode, final String className, final JPackage
131 | codeModelPackage) throws Exception {
132 | // Find the class if it exists, or create it if it doesn't
133 | JDefinedClass clazz;
134 | if (definedClasses.containsKey(className)) {
135 | clazz = definedClasses.get(className);
136 | } else {
137 | clazz = codeModelPackage._class(className);
138 | annotateClass(clazz);
139 | definedClasses.put(className, clazz);
140 | fieldMap.put(clazz, new TreeSet<>(fieldComparator));
141 | }
142 |
143 | // Iterate over all of the fields in this object
144 | Set> fieldsIterator = classNode.entrySet();
145 | for (Map.Entry entry : fieldsIterator) {
146 | String childProperty = entry.getKey();
147 | JsonElement childNode = entry.getValue();
148 |
149 | // Recurse into objects and arrays
150 | if (childNode.isJsonObject()) {
151 | String childName = formatClassName(childProperty);
152 | parseObject(childNode.getAsJsonObject(), childName, codeModelPackage);
153 | } else if (childNode.isJsonArray()) {
154 | String childName = formatClassName(Inflector.getInstance().singularize(childProperty));
155 | parseArray(childNode.getAsJsonArray(), childName, codeModelPackage);
156 | }
157 |
158 | // Now attempt to create the field and add it to the field set
159 | FieldInfo field = getFieldInfoFromNode(childNode, childProperty, codeModelPackage.owner());
160 | if (field != null) {
161 | fieldMap.get(clazz).add(field);
162 | }
163 | }
164 | }
165 |
166 | /**
167 | * Generates all of the sub-objects for a given array node.
168 | *
169 | * @param arrayNode the JSON array node in the JSON syntax tree.
170 | * @param className the formatted name of the class we might generate from this array.
171 | * @param codeModelPackage the code model package to generate the class in.
172 | * @throws Exception if an error occurs.
173 | */
174 | private void parseArray(JsonArray arrayNode, String className, JPackage codeModelPackage) throws
175 | Exception {
176 | // Retrieve the first non-null element of the array
177 | Iterator elementsIterator = arrayNode.iterator();
178 | while (elementsIterator.hasNext()) {
179 | JsonElement element = elementsIterator.next();
180 |
181 | // Recurse on the first object or array
182 | if (element.isJsonObject()) {
183 | parseObject(element.getAsJsonObject(), className, codeModelPackage);
184 | break;
185 | } else if (element.isJsonArray()) {
186 | parseArray(element.getAsJsonArray(), className, codeModelPackage);
187 | break;
188 | }
189 | }
190 | }
191 |
192 | /**
193 | * Creates a field in the given class.
194 | *
195 | * @param node the JSON node describing the field.
196 | * @param propertyName the name of the field to create.
197 | * @param codeModel the code model to use for generation.
198 | * @return a {@link FieldInfo} representing the new field.
199 | * @throws Exception if an error occurs.
200 | */
201 | private FieldInfo getFieldInfoFromNode(JsonElement node, String propertyName, JCodeModel
202 | codeModel) throws Exception {
203 | // Switch on node type
204 | if (node.isJsonArray()) {
205 | // Singularize the class name of a single element
206 | String newClassName = formatClassName(Inflector.getInstance().singularize(propertyName));
207 |
208 | // Get the array type
209 | JsonArray array = node.getAsJsonArray();
210 | if (array.iterator().hasNext()) {
211 | JsonElement firstNode = array.iterator().next();
212 | if (firstNode.isJsonObject()) {
213 | // Get the already-created class from the class map
214 | JDefinedClass newClass = definedClasses.get(newClassName);
215 |
216 | // Now return the field referring to a list of the new class
217 | return new FieldInfo(codeModel.ref(List.class).narrow(newClass), propertyName);
218 | } else if (firstNode.isJsonArray()) {
219 | // Recurse to get the field info of this node
220 | FieldInfo fi = getFieldInfoFromNode(firstNode, propertyName, codeModel);
221 |
222 | // Make a List<> of the recursed type
223 | return new FieldInfo(codeModel.ref(List.class).narrow(fi.type), propertyName);
224 | } else if (firstNode.isJsonNull()) {
225 | // Null values? Return List.
226 | return new FieldInfo(deferredList, propertyName);
227 | } else if (firstNode.isJsonPrimitive()) {
228 | JsonPrimitive primitiveNode = firstNode.getAsJsonPrimitive();
229 | if (primitiveNode.isNumber()) {
230 | Number n = node.getAsNumber();
231 | if (n.doubleValue() == n.longValue()) {
232 | return new FieldInfo(codeModel.ref(List.class).narrow(Long.class), propertyName);
233 | } else {
234 | return new FieldInfo(codeModel.ref(List.class).narrow(Double.class), propertyName);
235 | }
236 | } else if (primitiveNode.isJsonNull()) {
237 | // Null values? Return List.
238 | return new FieldInfo(deferredList, propertyName);
239 | } else if (primitiveNode.isString()) {
240 | // Now return the field referring to a list of strings
241 | return new FieldInfo(codeModel.ref(List.class).narrow(String.class), propertyName);
242 | }
243 | }
244 | } else {
245 | // No elements? Return List.
246 | return new FieldInfo(deferredList, propertyName);
247 | }
248 | } else if (node.isJsonPrimitive()) {
249 | return getPrimitiveFieldInfo(codeModel, node.getAsJsonPrimitive(), propertyName);
250 | } else if (node.isJsonNull()) {
251 | // Defer the type reference until later
252 | return new FieldInfo(deferredClass, propertyName);
253 | } else if (node.isJsonObject()) {
254 | // Get the already-created class from the class map
255 | String newClassName = formatClassName(propertyName);
256 | JDefinedClass newClass = definedClasses.get(newClassName);
257 |
258 | // Now return the field as a defined class
259 | return new FieldInfo(newClass, propertyName);
260 | }
261 |
262 | // If all else fails, return null
263 | return null;
264 | }
265 |
266 | private FieldInfo getPrimitiveFieldInfo(JCodeModel codeModel, JsonPrimitive node, String
267 | propertyName) {
268 | if (node.isBoolean()) {
269 | return new FieldInfo(config.isFieldTypePrimitive() ? codeModel.BOOLEAN
270 | : codeModel.ref(Boolean.class), propertyName);
271 | } else if (node.isNumber()) {
272 | Number n = node.getAsNumber();
273 | if (n.doubleValue() == n.longValue()) {
274 | return new FieldInfo(config.isFieldTypePrimitive() ? codeModel.LONG :
275 | codeModel.ref(Long.class), propertyName);
276 | } else {
277 | return new FieldInfo(config.isFieldTypePrimitive() ? codeModel.DOUBLE
278 | : codeModel.ref(Double.class), propertyName);
279 | }
280 | } else if (node.isString()) {
281 | return new FieldInfo(codeModel.ref(String.class), propertyName);
282 | }
283 | return new FieldInfo(deferredClass, propertyName);
284 | }
285 |
286 |
287 | /**
288 | * Generates all of the fields for a given class.
289 | *
290 | * @param clazz the class to generate sub-objects and fields for.
291 | * @param fields the set of fields to generate.
292 | * @param codeModel the code model.
293 | * @return a list of generated fields.
294 | * @throws Exception if an error occurs.
295 | */
296 | private List generateFields(JDefinedClass clazz, Set fields,
297 | JCodeModel codeModel) throws Exception {
298 | List generatedFields = new ArrayList<>();
299 |
300 | // Get sorted list of field names
301 | for (FieldInfo fieldInfo : fields) {
302 | // Create field with correct naming scheme
303 | String fieldName = formatFieldName(fieldInfo.propertyName);
304 |
305 | // Resolve deferred types
306 | JFieldVar newField;
307 | if (fieldInfo.type.equals(deferredClass)) {
308 | // Attempt to get the class from the class map
309 | String newClassName = formatClassName(fieldInfo.propertyName);
310 | JDefinedClass newClass = definedClasses.get(newClassName);
311 |
312 | // Now return the field for the actual class type
313 | if (newClass != null) {
314 | newField = clazz.field(JMod.PRIVATE, newClass, fieldName);
315 | } else {
316 | // Otherwise, just make a field of type Object
317 | newField = clazz.field(JMod.PRIVATE, codeModel.ref(Object.class), fieldName);
318 | }
319 | } else if (fieldInfo.type.equals(deferredList)) {
320 | // Attempt to get the class from the class map
321 | String newClassName = formatClassName(Inflector.getInstance()
322 | .singularize(fieldInfo.propertyName));
323 | JDefinedClass newClass = definedClasses.get(newClassName);
324 |
325 | // Now return the field referring to a list of the new class
326 | if (newClass != null) {
327 | newField = clazz.field(JMod.PRIVATE, codeModel.ref(List.class).narrow(newClass),
328 | fieldName);
329 | } else {
330 | // Otherwise, just make a field of type List
138 | *
139 | * Note that if the {@link Object#toString()} is called on the supplied object, so this method works for non-strings, too.
140 | *
141 | *
142 | * @param word the word that is to be pluralized.
143 | * @return the pluralized form of the word, or the word itself if it could not be pluralized
144 | * @see #singularize(Object)
145 | */
146 | public String pluralize(Object word) {
147 | if (word == null) return null;
148 | String wordStr = word.toString().trim();
149 | if (wordStr.length() == 0) return wordStr;
150 | if (isUncountable(wordStr)) return wordStr;
151 | for (Rule rule : this.plurals) {
152 | String result = rule.apply(wordStr);
153 | if (result != null) return result;
154 | }
155 | return wordStr;
156 | }
157 |
158 | public String pluralize(Object word,
159 | int count) {
160 | if (word == null) return null;
161 | if (count == 1 || count == -1) {
162 | return word.toString();
163 | }
164 | return pluralize(word);
165 | }
166 |
167 | /**
168 | * Returns the singular form of the word in the string.
169 | *
183 | * Note that if the {@link Object#toString()} is called on the supplied object, so this method works for non-strings, too.
184 | *
185 | *
186 | * @param word the word that is to be pluralized.
187 | * @return the pluralized form of the word, or the word itself if it could not be pluralized
188 | * @see #pluralize(Object)
189 | */
190 | public String singularize(Object word) {
191 | if (word == null) return null;
192 | String wordStr = word.toString().trim();
193 | if (wordStr.length() == 0) return wordStr;
194 | if (isUncountable(wordStr)) return wordStr;
195 | for (Rule rule : this.singulars) {
196 | String result = rule.apply(wordStr);
197 | if (result != null) return result;
198 | }
199 | return wordStr;
200 | }
201 |
202 | /**
203 | * Converts strings to lowerCamelCase. This method will also use any extra delimiter characters to identify word boundaries.
204 | *
213 | *
214 | *
215 | *
216 | * @param lowerCaseAndUnderscoredWord the word that is to be converted to camel case
217 | * @param delimiterChars optional characters that are used to delimit word boundaries
218 | * @return the lower camel case version of the word
219 | * @see #underscore(String, char[])
220 | * @see #camelCase(String, boolean, char[])
221 | * @see #upperCamelCase(String, char[])
222 | */
223 | public String lowerCamelCase(String lowerCaseAndUnderscoredWord,
224 | char... delimiterChars) {
225 | return camelCase(lowerCaseAndUnderscoredWord, false, delimiterChars);
226 | }
227 |
228 | /**
229 | * Converts strings to UpperCamelCase. This method will also use any extra delimiter characters to identify word boundaries.
230 | *
239 | *
240 | *
241 | *
242 | * @param lowerCaseAndUnderscoredWord the word that is to be converted to camel case
243 | * @param delimiterChars optional characters that are used to delimit word boundaries
244 | * @return the upper camel case version of the word
245 | * @see #underscore(String, char[])
246 | * @see #camelCase(String, boolean, char[])
247 | * @see #lowerCamelCase(String, char[])
248 | */
249 | public String upperCamelCase(String lowerCaseAndUnderscoredWord,
250 | char... delimiterChars) {
251 | return camelCase(lowerCaseAndUnderscoredWord, true, delimiterChars);
252 | }
253 |
254 | /**
255 | * By default, this method converts strings to UpperCamelCase. If the uppercaseFirstLetter argument to false,
256 | * then this method produces lowerCamelCase. This method will also use any extra delimiter characters to identify word
257 | * boundaries.
258 | *
269 | *
270 | *
271 | *
272 | * @param lowerCaseAndUnderscoredWord the word that is to be converted to camel case
273 | * @param uppercaseFirstLetter true if the first character is to be uppercased, or false if the first character is to be
274 | * lowercased
275 | * @param delimiterChars optional characters that are used to delimit word boundaries
276 | * @return the camel case version of the word
277 | * @see #underscore(String, char[])
278 | * @see #upperCamelCase(String, char[])
279 | * @see #lowerCamelCase(String, char[])
280 | */
281 | public String camelCase(String lowerCaseAndUnderscoredWord,
282 | boolean uppercaseFirstLetter,
283 | char... delimiterChars) {
284 | if (lowerCaseAndUnderscoredWord == null) return null;
285 | lowerCaseAndUnderscoredWord = lowerCaseAndUnderscoredWord.trim();
286 | if (lowerCaseAndUnderscoredWord.length() == 0) return "";
287 | if (uppercaseFirstLetter) {
288 | String result = lowerCaseAndUnderscoredWord;
289 | // Replace any extra delimiters with underscores (before the underscores are converted in the next step)...
290 | if (delimiterChars != null) {
291 | for (char delimiterChar : delimiterChars) {
292 | result = result.replace(delimiterChar, '_');
293 | }
294 | }
295 |
296 | // Change the case at the beginning at after each underscore ...
297 | return replaceAllWithUppercase(result, "(^|_)(.)", 2);
298 | }
299 | if (lowerCaseAndUnderscoredWord.length() < 2) return lowerCaseAndUnderscoredWord;
300 | return "" + Character.toLowerCase(lowerCaseAndUnderscoredWord.charAt(0))
301 | + camelCase(lowerCaseAndUnderscoredWord, true, delimiterChars).substring(1);
302 | }
303 |
304 | /**
305 | * Makes an underscored form from the expression in the string (the reverse of the {@link #camelCase(String, boolean, char[])
306 | * camelCase} method. Also changes any characters that match the supplied delimiters into underscore.
307 | *
318 | *
319 | *
320 | *
321 | * @param camelCaseWord the camel-cased word that is to be converted;
322 | * @param delimiterChars optional characters that are used to delimit word boundaries (beyond capitalization)
323 | * @return a lower-cased version of the input, with separate words delimited by the underscore character.
324 | */
325 | public String underscore(String camelCaseWord,
326 | char... delimiterChars) {
327 | if (camelCaseWord == null) return null;
328 | String result = camelCaseWord.trim();
329 | if (result.length() == 0) return "";
330 | result = result.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2");
331 | result = result.replaceAll("([a-z\\d])([A-Z])", "$1_$2");
332 | result = result.replace('-', '_');
333 | if (delimiterChars != null) {
334 | for (char delimiterChar : delimiterChars) {
335 | result = result.replace(delimiterChar, '_');
336 | }
337 | }
338 | return result.toLowerCase();
339 | }
340 |
341 | /**
342 | * Returns a copy of the input with the first character converted to uppercase and the remainder to lowercase.
343 | *
344 | * @param words the word to be capitalized
345 | * @return the string with the first character capitalized and the remaining characters lowercased
346 | */
347 | public String capitalize(String words) {
348 | if (words == null) return null;
349 | String result = words.trim();
350 | if (result.length() == 0) return "";
351 | if (result.length() == 1) return result.toUpperCase();
352 | return "" + Character.toUpperCase(result.charAt(0)) + result.substring(1).toLowerCase();
353 | }
354 |
355 | /**
356 | * Capitalizes the first word and turns underscores into spaces and strips trailing "_id" and any supplied removable tokens.
357 | * Like {@link #titleCase(String, String[])}, this is meant for creating pretty output.
358 | *
365 | *
366 | *
367 | *
368 | * @param lowerCaseAndUnderscoredWords the input to be humanized
369 | * @param removableTokens optional array of tokens that are to be removed
370 | * @return the humanized string
371 | * @see #titleCase(String, String[])
372 | */
373 | public String humanize(String lowerCaseAndUnderscoredWords,
374 | String... removableTokens) {
375 | if (lowerCaseAndUnderscoredWords == null) return null;
376 | String result = lowerCaseAndUnderscoredWords.trim();
377 | if (result.length() == 0) return "";
378 | // Remove a trailing "_id" token
379 | result = result.replaceAll("_id$", "");
380 | // Remove all of the tokens that should be removed
381 | if (removableTokens != null) {
382 | for (String removableToken : removableTokens) {
383 | result = result.replaceAll(removableToken, "");
384 | }
385 | }
386 | result = result.replaceAll("_+", " "); // replace all adjacent underscores with a single space
387 | return capitalize(result);
388 | }
389 |
390 | /**
391 | * Capitalizes all the words and replaces some characters in the string to create a nicer looking title. Underscores are
392 | * changed to spaces, a trailing "_id" is removed, and any of the supplied tokens are removed. Like
393 | * {@link #humanize(String, String[])}, this is meant for creating pretty output.
394 | *
395 | * Examples:
396 | *
397 | *
398 | * inflector.titleCase("man from the boondocks") #=> "Man From The Boondocks"
399 | * inflector.titleCase("x-men: the last stand") #=> "X Men: The Last Stand"
400 | *
401 | *
402 | *
403 | *
404 | * @param words the input to be turned into title case
405 | * @param removableTokens optional array of tokens that are to be removed
406 | * @return the title-case version of the supplied words
407 | */
408 | public String titleCase(String words,
409 | String... removableTokens) {
410 | String result = humanize(words, removableTokens);
411 | result = replaceAllWithUppercase(result, "\\b([a-z])", 1); // change first char of each word to uppercase
412 | return result;
413 | }
414 |
415 | /**
416 | * Turns a non-negative number into an ordinal string used to denote the position in an ordered sequence, such as 1st, 2nd,
417 | * 3rd, 4th.
418 | *
419 | * @param number the non-negative number
420 | * @return the string with the number and ordinal suffix
421 | */
422 | public String ordinalize(int number) {
423 | int remainder = number % 100;
424 | String numberStr = Integer.toString(number);
425 | if (11 <= number && number <= 13) return numberStr + "th";
426 | remainder = number % 10;
427 | if (remainder == 1) return numberStr + "st";
428 | if (remainder == 2) return numberStr + "nd";
429 | if (remainder == 3) return numberStr + "rd";
430 | return numberStr + "th";
431 | }
432 |
433 | // ------------------------------------------------------------------------------------------------
434 | // Management methods
435 | // ------------------------------------------------------------------------------------------------
436 |
437 | /**
438 | * Determine whether the supplied word is considered uncountable by the {@link #pluralize(Object) pluralize} and
439 | * {@link #singularize(Object) singularize} methods.
440 | *
441 | * @param word the word
442 | * @return true if the plural and singular forms of the word are the same
443 | */
444 | public boolean isUncountable(String word) {
445 | if (word == null) return false;
446 | String trimmedLower = word.trim().toLowerCase();
447 | return this.uncountables.contains(trimmedLower);
448 | }
449 |
450 | /**
451 | * Get the set of words that are not processed by the Inflector. The resulting map is directly modifiable.
452 | *
453 | * @return the set of uncountable words
454 | */
455 | public Set getUncountables() {
456 | return uncountables;
457 | }
458 |
459 | public void addPluralize(String rule,
460 | String replacement) {
461 | final Rule pluralizeRule = new Rule(rule, replacement);
462 | this.plurals.addFirst(pluralizeRule);
463 | }
464 |
465 | public void addSingularize(String rule,
466 | String replacement) {
467 | final Rule singularizeRule = new Rule(rule, replacement);
468 | this.singulars.addFirst(singularizeRule);
469 | }
470 |
471 | public void addIrregular(String singular,
472 | String plural) {
473 | if (singular == null || singular.isEmpty()) {
474 | throw new IllegalArgumentException("singular");
475 | }
476 | if (plural == null || plural.isEmpty()) {
477 | throw new IllegalArgumentException("plural");
478 | }
479 | String singularRemainder = singular.length() > 1 ? singular.substring(1) : "";
480 | String pluralRemainder = plural.length() > 1 ? plural.substring(1) : "";
481 | addPluralize("(" + singular.charAt(0) + ")" + singularRemainder + "$", "$1" + pluralRemainder);
482 | addSingularize("(" + plural.charAt(0) + ")" + pluralRemainder + "$", "$1" + singularRemainder);
483 | }
484 |
485 | public void addUncountable(String... words) {
486 | if (words == null || words.length == 0) return;
487 | for (String word : words) {
488 | if (word != null) uncountables.add(word.trim().toLowerCase());
489 | }
490 | }
491 |
492 | /**
493 | * Utility method to replace all occurrences given by the specific backreference with its uppercased form, and remove all
494 | * other backreferences.
495 | *
496 | * The Java {@link Pattern regular expression processing} does not use the preprocessing directives \l,
497 | * \u, \L, and \U. If so, such directives could be used in the replacement string
498 | * to uppercase or lowercase the backreferences. For example, \L1 would lowercase the first backreference, and
499 | * \u3 would uppercase the 3rd backreference.
500 | *