├── .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 | ![Context menu](image/menu.jpg "菜单") 13 | 14 | 2. 输入类名和源JSON文本。 15 | 16 | ![Input UI](image/input_zh.jpg "输入") 17 | 18 | ## 配置 19 | ![Settings UI](image/config_zh.jpg "配置界面") 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 | ![Context menu](image/menu.jpg "菜单") 13 | 14 | 2. Input root class name and source JSON. 15 | 16 | ![Input UI](image/input.jpg "输入") 17 | 18 | ## Settings 19 | ![Settings UI](image/config.jpg) 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 331 | newField = clazz.field(JMod.PRIVATE, codeModel.ref(List.class).narrow(Object.class), 332 | fieldName); 333 | } 334 | } else { 335 | // The type should already be defined so just use it 336 | newField = clazz.field(JMod.PRIVATE, fieldInfo.type, fieldName); 337 | } 338 | 339 | if (newField != null) { 340 | // Annotate field 341 | annotateField(newField, fieldInfo.propertyName); 342 | 343 | // Create getter/setter if lombok.Data is not present. 344 | if (!config.isLombokData()) { 345 | createGetter(clazz, newField, fieldInfo.propertyName); 346 | createSetter(clazz, newField, fieldInfo.propertyName); 347 | } 348 | 349 | // Add field to return list 350 | generatedFields.add(new GeneratedField(newField, fieldInfo.propertyName)); 351 | } 352 | } 353 | 354 | return generatedFields; 355 | } 356 | 357 | /** 358 | * Adds the annotations to the class. 359 | * 360 | * @param clazz the class to annotate. 361 | */ 362 | private static void annotateClass(final JDefinedClass clazz) throws ClassNotFoundException { 363 | if (config.isLombokNoArgsConstructor()) { 364 | clazz.annotate(NoArgsConstructor.class); 365 | } 366 | 367 | if (config.isLombokRequiredArgsConstructor()) { 368 | clazz.annotate(RequiredArgsConstructor.class); 369 | } 370 | 371 | if (config.isLombokAllArgsConstructor()) { 372 | clazz.annotate(AllArgsConstructor.class); 373 | } 374 | 375 | if (config.isLombokData()) { 376 | clazz.annotate(Data.class); 377 | } 378 | 379 | if (config.useAccessors()) { 380 | JAnnotationUse annotation = clazz.annotate(Accessors.class); 381 | if (config.isLombokAccessorsFluent()) { 382 | annotation.param("fluent", true); 383 | } 384 | if (config.isLombokAccessorsChain()) { 385 | annotation.param("chain", true); 386 | } 387 | if (!config.getLombokAccessorsPrefix().trim().isEmpty()) { 388 | annotation.param("prefix", config.getLombokAccessorsPrefix()); 389 | } 390 | } 391 | 392 | if (config.isLombokBuilder()) { 393 | clazz.annotate(Builder.class); 394 | } 395 | 396 | List suppressWarnings = Arrays.asList(config.getSuppressWarnings().trim() 397 | .split("\\,|\\;|\\ ")) 398 | .stream().filter(x -> !x.isEmpty()).collect(Collectors.toList()); 399 | if (suppressWarnings.size() > 0) { 400 | JAnnotationUse annotation = clazz.annotate(SuppressWarnings.class); 401 | if (suppressWarnings.size() == 1) { 402 | annotation.param("value", suppressWarnings.get(0)); 403 | } else { 404 | JAnnotationArrayMember member = annotation.paramArray("value"); 405 | for (String value : suppressWarnings) { 406 | if (!value.isEmpty()) { 407 | member.param(value); 408 | } 409 | } 410 | } 411 | } 412 | } 413 | 414 | /** 415 | * Adds potentially the {@link SerializedName} annotation to a given 416 | * field - the latter is applied only if the property name differs from the field name. 417 | * 418 | * @param field the field to annotate. 419 | * @param propertyName the original JSON property name. 420 | */ 421 | private static void annotateField(JFieldVar field, String propertyName) { 422 | // Use the SerializedName annotation if the field name doesn't match the property name 423 | if (!field.name().equals(propertyName)) { 424 | if (config.getFieldNameAnnotation() == 1) { 425 | field.annotate(SerializedName.class).param("value", propertyName); 426 | } else if (config.getFieldNameAnnotation() == 2) { 427 | field.annotate(JsonProperty.class).param("value", propertyName); 428 | } 429 | } 430 | } 431 | 432 | /** 433 | * Generates a getter for the given class, field, and property name. 434 | * 435 | * @param clazz the class to generate a getter in. 436 | * @param field the field to return. 437 | * @param propertyName the name of the property. 438 | * @return a {@link JMethod} which is a getter for the given field. 439 | */ 440 | private static JMethod createGetter(final JDefinedClass clazz, final JFieldVar field, 441 | final String propertyName) { 442 | // Method name should start with "get" and then the uppercased class name. 443 | JMethod getter = clazz.method(JMod.PUBLIC, field.type(), "get" + formatClassName(propertyName)); 444 | 445 | // Return the field 446 | JBlock body = getter.body(); 447 | body._return(field); 448 | return getter; 449 | } 450 | 451 | /** 452 | * Generates a setter for the given class, field, and property name. 453 | * 454 | * @param clazz the class to generate a setter in. 455 | * @param field the field to set. 456 | * @param propertyName the name of the property. 457 | * @return a {@link JMethod} which is a setter for the given field. 458 | */ 459 | private static JMethod createSetter(JDefinedClass clazz, JFieldVar field, String propertyName) { 460 | // Method name should start with "set" and then the uppercased class name 461 | JMethod setter = clazz.method(JMod.PUBLIC, void.class, "set" + formatClassName(propertyName)); 462 | 463 | // Set parameter name to lower camel case 464 | String paramName = sanitizePropertyName(propertyName); 465 | JVar param = setter.param(field.type(), paramName); 466 | 467 | // Assign to field name 468 | JBlock body = setter.body(); 469 | if (field.name().equals(paramName)) { 470 | // Assign this.FieldName = paramName 471 | body.assign(JExpr._this().ref(field), param); 472 | } else { 473 | // Safe to just assign FieldName = paramName 474 | body.assign(field, param); 475 | } 476 | return setter; 477 | } 478 | 479 | /** 480 | * Formats the given property name into a more standard class name. 481 | * 482 | * @param propertyName the original property name. 483 | * @return the formatted class name. 484 | */ 485 | static String formatClassName(String propertyName) { 486 | return StringUtils.capitalize(sanitizePropertyName(propertyName)); 487 | } 488 | 489 | /** 490 | * Formats the given property name into a more standard field name. 491 | * 492 | * @param propertyName the original property name. 493 | * @return the formatted field name. 494 | */ 495 | static String formatFieldName(String propertyName) { 496 | return config.getLombokAccessorsPrefix().trim() + sanitizePropertyName(propertyName); 497 | } 498 | 499 | /** 500 | * Given a property name as a string, creates a valid identifier by removing non-alphanumeric 501 | * characters and uppercasing the letters after non-alphanumeric characters. 502 | * 503 | * @param propertyName the property name to format. 504 | * @return a String containing uppercased words, with underscores removed. 505 | */ 506 | private static String sanitizePropertyName(String propertyName) { 507 | final StringBuilder formattedName = new StringBuilder(); 508 | boolean uppercaseNext = false; 509 | 510 | // Avoid invalid starting characters for class / field names 511 | if (Character.isJavaIdentifierStart(propertyName.charAt(0))) { 512 | formattedName.append(Character.toLowerCase(propertyName.charAt(0))); 513 | } 514 | 515 | // Iterate over the other characters 516 | for (int charIndex = 1; charIndex < propertyName.length(); charIndex++) { 517 | // Append valid characters 518 | Character c = propertyName.charAt(charIndex); 519 | if (Character.isAlphabetic(c)) { 520 | if (uppercaseNext) { 521 | // Uppercase this letter 522 | formattedName.append(Character.toUpperCase(c)); 523 | uppercaseNext = false; 524 | } else { 525 | // Retain case, lowers for first 526 | formattedName.append(formattedName.length() == 0 ? Character.toLowerCase(c) : c); 527 | } 528 | } else if (Character.isDigit(c)) { 529 | // Append as is 530 | formattedName.append(c); 531 | } else { 532 | // Don't append non-alphanumeric parts and uppercase next letter 533 | uppercaseNext = formattedName.length() > 0; 534 | } 535 | } 536 | 537 | if (formattedName.length() == 0) { 538 | throw new IllegalArgumentException("Illegal property name: \"" + propertyName + "\"."); 539 | } 540 | 541 | return formattedName.toString(); 542 | } 543 | 544 | /** 545 | * A class type that indicates that we don't yet know the type of data this field represents. 546 | */ 547 | private static class Deferred { 548 | 549 | } 550 | 551 | /** 552 | * A comparator that sorts field data objects by field name, case insensitive. 553 | */ 554 | private static class FieldComparator implements Comparator { 555 | @Override 556 | public int compare(FieldInfo left, FieldInfo right) { 557 | // Sort by formatted field name, not the property names 558 | return formatFieldName(left.propertyName) 559 | .compareTo(formatFieldName(right.propertyName)); 560 | } 561 | } 562 | 563 | /** 564 | * A simple representation of a field to be created. 565 | */ 566 | @Data 567 | @AllArgsConstructor 568 | private static class FieldInfo { 569 | private JType type; 570 | private String propertyName; 571 | } 572 | 573 | /** 574 | * A pair containing a generated {@link JFieldVar} field and its original property name. 575 | */ 576 | @Data 577 | @AllArgsConstructor 578 | private static class GeneratedField { 579 | private JFieldVar field; 580 | private String propertyName; 581 | } 582 | } 583 | -------------------------------------------------------------------------------- /src/main/java/liwey/json2pojo/GeneratorDialog.form: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /src/main/java/liwey/json2pojo/GeneratorDialog.java: -------------------------------------------------------------------------------- 1 | package liwey.json2pojo; 2 | 3 | import java.awt.*; 4 | import java.awt.event.*; 5 | import java.io.IOException; 6 | import java.io.PrintWriter; 7 | import java.io.StringWriter; 8 | 9 | import javax.swing.*; 10 | import javax.swing.event.DocumentEvent; 11 | import javax.swing.event.DocumentListener; 12 | 13 | import static liwey.json2pojo.ConfigUtil.config; 14 | 15 | /** 16 | * A custom dialog which allows the user to input a JSON text. 17 | */ 18 | public class GeneratorDialog extends JDialog { 19 | private static final String CLASS_NAME_REGEX = "[A-Za-z][A-Za-z0-9]*"; 20 | 21 | private final String packageName; 22 | private final String destPath; 23 | 24 | // UI 25 | private JButton buttonCancel; 26 | private JButton buttonGenerate; 27 | private JTextField txtClassName; 28 | private JPanel contentPanel; 29 | private JButton buttonSettings; 30 | private JTextArea jsonTextArea; 31 | private JTextField resultTextField; 32 | 33 | GeneratorDialog(String packageName, String destPath) { 34 | this.packageName = packageName; 35 | this.destPath = destPath; 36 | 37 | // Set up the main content 38 | setContentPane(contentPanel); 39 | setModal(true); 40 | setTitle(R.get("title")); 41 | setLocation(config.getWindowX(), config.getWindowY()); 42 | setSize(config.getWindowWidth(), config.getWindowHeight()); 43 | getRootPane().setDefaultButton(buttonGenerate); 44 | 45 | // Set the minimum dialog size 46 | setMinimumSize(new Dimension(400, 400)); 47 | 48 | // Add button listeners 49 | buttonGenerate.addActionListener(e -> onGenerate()); 50 | buttonCancel.addActionListener(e -> onCancel()); 51 | 52 | // Call onCancel() when cross is clicked 53 | setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); 54 | addWindowListener(new WindowAdapter() { 55 | public void windowClosing(WindowEvent e) { 56 | onCancel(); 57 | } 58 | 59 | @Override 60 | public void windowClosed(WindowEvent e) { 61 | saveDialogPosAndSize(); 62 | } 63 | }); 64 | 65 | // Call onCancel() on ESCAPE 66 | contentPanel.registerKeyboardAction(e -> onCancel(), 67 | KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), 68 | JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 69 | 70 | // Enable/disable OK button 71 | buttonGenerate.setEnabled(false); 72 | jsonTextArea.setBorder(BorderFactory.createCompoundBorder(null, BorderFactory.createEmptyBorder(2, 5, 2, 5))); 73 | txtClassName.getDocument().addDocumentListener(new TextChangedListener()); 74 | jsonTextArea.getDocument().addDocumentListener(new TextChangedListener()); 75 | 76 | buttonSettings.addActionListener(e -> { 77 | SettingsDialog dialog = new SettingsDialog(); 78 | dialog.setTitle(R.get("settings")); 79 | dialog.setLocationRelativeTo(this); 80 | dialog.pack(); 81 | dialog.setVisible(true); 82 | }); 83 | } 84 | 85 | private void onCancel() { 86 | dispose(); 87 | } 88 | 89 | private void onGenerate() { 90 | new Thread(() -> { 91 | Generator generator = new Generator(packageName, destPath, resultTextField); 92 | try { 93 | int n = generator.generateFromJson(txtClassName.getText(), jsonTextArea.getText()); 94 | resultTextField.setForeground(Color.blue); 95 | resultTextField.setText(R.get("generate.result", n, packageName)); 96 | resultTextField.setToolTipText(R.get("success")); 97 | //dispose(); 98 | } catch (Exception e) { 99 | Throwable cause = e; 100 | while (cause.getCause() != null) 101 | cause = e.getCause(); 102 | resultTextField.setForeground(Color.red); 103 | resultTextField.setText(cause.getMessage()); 104 | StringWriter writer = new StringWriter(); 105 | cause.printStackTrace(new PrintWriter(writer)); 106 | resultTextField.setToolTipText("" + writer.toString().replace("\n", "
") + ""); 107 | } 108 | }).start(); 109 | } 110 | 111 | private void createUIComponents() { 112 | } 113 | 114 | private void saveDialogPosAndSize() { 115 | config.setWindowWidth(this.getWidth()); 116 | config.setWindowHeight(this.getHeight()); 117 | config.setWindowX(this.getX()); 118 | config.setWindowY(this.getY()); 119 | try { 120 | ConfigUtil.save(); 121 | } catch (IOException e) { 122 | e.printStackTrace(); 123 | } 124 | } 125 | 126 | /** 127 | * Gets called when the JSON text or root class text has changed. 128 | */ 129 | private class TextChangedListener implements DocumentListener { 130 | @Override 131 | public void insertUpdate(DocumentEvent e) { 132 | validate(); 133 | } 134 | 135 | @Override 136 | public void removeUpdate(DocumentEvent e) { 137 | validate(); 138 | } 139 | 140 | @Override 141 | public void changedUpdate(DocumentEvent e) { 142 | validate(); 143 | } 144 | 145 | /** 146 | * Validates the class name and JSON text and enables the OK button if validation passes. 147 | */ 148 | private void validate() { 149 | buttonGenerate.setEnabled(txtClassName.getText().matches(CLASS_NAME_REGEX) && !jsonTextArea.getText().isEmpty()); 150 | } 151 | } 152 | 153 | public static void main(String[] args) { 154 | ConfigUtil.setLocale(); 155 | 156 | GeneratorDialog dialog = new GeneratorDialog("test", System.getProperty("user.dir") + "/src/test/java"); 157 | dialog.setLocation(config.getWindowX(), config.getWindowY()); 158 | dialog.setSize(config.getWindowWidth(), config.getWindowHeight()); 159 | dialog.setVisible(true); 160 | System.exit(0); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/main/java/liwey/json2pojo/R.java: -------------------------------------------------------------------------------- 1 | package liwey.json2pojo; 2 | 3 | import java.text.MessageFormat; 4 | import java.util.Locale; 5 | import java.util.ResourceBundle; 6 | 7 | /** 8 | * Resources helper, name like android. 9 | * 10 | * @author Leon Zeng 11 | * @since 2019/1/2 15:37 12 | */ 13 | public class R { 14 | public static String get(String key, Object... params) { 15 | ResourceBundle bundle = ResourceBundle.getBundle("bundles", Locale.getDefault()); 16 | return MessageFormat.format(bundle.getString(key), params); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/liwey/json2pojo/SettingsDialog.form: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /src/main/java/liwey/json2pojo/SettingsDialog.java: -------------------------------------------------------------------------------- 1 | package liwey.json2pojo; 2 | 3 | import java.awt.event.KeyEvent; 4 | import java.awt.event.WindowAdapter; 5 | import java.awt.event.WindowEvent; 6 | import java.io.IOException; 7 | 8 | import javax.swing.*; 9 | 10 | import static liwey.json2pojo.ConfigUtil.config; 11 | 12 | public class SettingsDialog extends JDialog { 13 | private JPanel contentPanel; 14 | private JButton buttonOK; 15 | private JButton buttonCancel; 16 | 17 | private JCheckBox dataCheckbox; 18 | private JCheckBox builderCheckbox; 19 | private JCheckBox noArgsConstructorCheckBox; 20 | private JCheckBox requiredArgsConstructorCheckBox; 21 | private JCheckBox allArgsConstructorCheckBox; 22 | private JCheckBox fluentCheckBox; 23 | 24 | private JComboBox filedNameComboBox; 25 | private JCheckBox checkBoxPrimitive; 26 | private JCheckBox chainCheckBox; 27 | private JTextField prefixTextField; 28 | private JTextField warningsTextField; 29 | 30 | public SettingsDialog() { 31 | setContentPane(contentPanel); 32 | setModal(true); 33 | setResizable(false); 34 | getRootPane().setDefaultButton(buttonOK); 35 | 36 | buttonOK.addActionListener(e -> onOK()); 37 | 38 | buttonCancel.addActionListener(e -> onCancel()); 39 | 40 | // call onCancel() when cross is clicked 41 | setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); 42 | addWindowListener(new WindowAdapter() { 43 | public void windowClosing(WindowEvent e) { 44 | onCancel(); 45 | } 46 | }); 47 | 48 | // call onCancel() on ESCAPE 49 | contentPanel.registerKeyboardAction(e -> onCancel(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), 50 | JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 51 | 52 | filedNameComboBox.addItem("[None]"); 53 | filedNameComboBox.addItem("@SerializedName (gson)"); 54 | filedNameComboBox.addItem("@JsonProperty (jackson)"); 55 | 56 | bindConfig(); 57 | } 58 | 59 | private void bindConfig() { 60 | setData(config); 61 | filedNameComboBox.setSelectedIndex(config.getFieldNameAnnotation()); 62 | } 63 | 64 | private void onOK() { 65 | try { 66 | getData(config); 67 | config.setFieldNameAnnotation(filedNameComboBox.getSelectedIndex()); 68 | ConfigUtil.save(); 69 | } catch (IOException e) { 70 | JOptionPane.showMessageDialog(this, "Failed to load settings: " + e.getMessage(), "Error", 71 | JOptionPane.ERROR_MESSAGE); 72 | } 73 | dispose(); 74 | } 75 | 76 | private void onCancel() { 77 | dispose(); 78 | } 79 | 80 | public void setData(Config data) { 81 | noArgsConstructorCheckBox.setSelected(data.isLombokNoArgsConstructor()); 82 | requiredArgsConstructorCheckBox.setSelected(data.isLombokRequiredArgsConstructor()); 83 | allArgsConstructorCheckBox.setSelected(data.isLombokAllArgsConstructor()); 84 | dataCheckbox.setSelected(data.isLombokData()); 85 | builderCheckbox.setSelected(data.isLombokBuilder()); 86 | fluentCheckBox.setSelected(data.isLombokAccessorsFluent()); 87 | chainCheckBox.setSelected(data.isLombokAccessorsChain()); 88 | prefixTextField.setText(data.getLombokAccessorsPrefix()); 89 | warningsTextField.setText(data.getSuppressWarnings()); 90 | checkBoxPrimitive.setSelected(data.isFieldTypePrimitive()); 91 | } 92 | 93 | public void getData(Config data) { 94 | data.setLombokNoArgsConstructor(noArgsConstructorCheckBox.isSelected()); 95 | data.setLombokRequiredArgsConstructor(requiredArgsConstructorCheckBox.isSelected()); 96 | data.setLombokAllArgsConstructor(allArgsConstructorCheckBox.isSelected()); 97 | data.setLombokData(dataCheckbox.isSelected()); 98 | data.setLombokBuilder(builderCheckbox.isSelected()); 99 | data.setLombokAccessorsFluent(fluentCheckBox.isSelected()); 100 | data.setLombokAccessorsChain(chainCheckBox.isSelected()); 101 | data.setLombokAccessorsPrefix(prefixTextField.getText()); 102 | data.setSuppressWarnings(warningsTextField.getText()); 103 | data.setFieldTypePrimitive(checkBoxPrimitive.isSelected()); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/dna/common/text/Inflector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss DNA (http://www.jboss.org/dna) 3 | * See the COPYRIGHT.txt file distributed with this work for information 4 | * regarding copyright ownership. Some portions may be licensed 5 | * to Red Hat, Inc. under one or more contributor license agreements. 6 | * See the AUTHORS.txt file in the distribution for a full listing of 7 | * individual contributors. 8 | * 9 | * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA 10 | * is licensed to you under the terms of the GNU Lesser General Public License as 11 | * published by the Free Software Foundation; either version 2.1 of 12 | * the License, or (at your option) any later version. 13 | * 14 | * JBoss DNA is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | * Lesser General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU Lesser General Public 20 | * License along with this software; if not, write to the Free 21 | * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 22 | * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 23 | */ 24 | package org.jboss.dna.common.text; 25 | 26 | import net.jcip.annotations.ThreadSafe; 27 | 28 | import java.util.HashSet; 29 | import java.util.LinkedList; 30 | import java.util.Set; 31 | import java.util.regex.Matcher; 32 | import java.util.regex.Pattern; 33 | 34 | /** 35 | * Transforms words to singular, plural, humanized (human readable), underscore, camel case, or ordinal form. This is inspired by 36 | * the Inflector class in Ruby on Rails, which is distributed under the Rails license. 39 | */ 40 | @ThreadSafe 41 | public class Inflector implements Cloneable { 42 | 43 | protected static final Inflector INSTANCE = new Inflector(); 44 | 45 | public static final Inflector getInstance() { 46 | return INSTANCE; 47 | } 48 | 49 | protected class Rule { 50 | 51 | protected final String expression; 52 | protected final Pattern expressionPattern; 53 | protected final String replacement; 54 | 55 | protected Rule(String expression, 56 | String replacement) { 57 | this.expression = expression; 58 | this.replacement = replacement != null ? replacement : ""; 59 | this.expressionPattern = Pattern.compile(this.expression, Pattern.CASE_INSENSITIVE); 60 | } 61 | 62 | /** 63 | * Apply the rule against the input string, returning the modified string or null if the rule didn't apply (and no 64 | * modifications were made) 65 | * 66 | * @param input the input string 67 | * @return the modified string if this rule applied, or null if the input was not modified by this rule 68 | */ 69 | protected String apply(String input) { 70 | Matcher matcher = this.expressionPattern.matcher(input); 71 | if (!matcher.find()) return null; 72 | return matcher.replaceAll(this.replacement); 73 | } 74 | 75 | @Override 76 | public int hashCode() { 77 | return expression.hashCode(); 78 | } 79 | 80 | @Override 81 | public boolean equals(Object obj) { 82 | if (obj == this) return true; 83 | if (obj != null && obj.getClass() == this.getClass()) { 84 | final Rule that = (Rule) obj; 85 | if (this.expression.equalsIgnoreCase(that.expression)) return true; 86 | } 87 | return false; 88 | } 89 | 90 | @Override 91 | public String toString() { 92 | return expression + ", " + replacement; 93 | } 94 | } 95 | 96 | private LinkedList plurals = new LinkedList(); 97 | private LinkedList singulars = new LinkedList(); 98 | /** 99 | * The lowercase words that are to be excluded and not processed. This map can be modified by the users via 100 | * {@link #getUncountables()}. 101 | */ 102 | private final Set uncountables = new HashSet(); 103 | 104 | public Inflector() { 105 | initialize(); 106 | } 107 | 108 | protected Inflector(Inflector original) { 109 | this.plurals.addAll(original.plurals); 110 | this.singulars.addAll(original.singulars); 111 | this.uncountables.addAll(original.uncountables); 112 | } 113 | 114 | @Override 115 | public Inflector clone() { 116 | return new Inflector(this); 117 | } 118 | 119 | // ------------------------------------------------------------------------------------------------ 120 | // Usage functions 121 | // ------------------------------------------------------------------------------------------------ 122 | 123 | /** 124 | * Returns the plural form of the word in the string. 125 | *

126 | * Examples: 127 | * 128 | *

129 |    *   inflector.pluralize("post")               #=> "posts"
130 |    *   inflector.pluralize("octopus")            #=> "octopi"
131 |    *   inflector.pluralize("sheep")              #=> "sheep"
132 |    *   inflector.pluralize("words")              #=> "words"
133 |    *   inflector.pluralize("the blue mailman")   #=> "the blue mailmen"
134 |    *   inflector.pluralize("CamelOctopus")       #=> "CamelOctopi"
135 |    * 
136 | * 137 | *

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 | *

170 | * Examples: 171 | * 172 | *

173 |    *   inflector.singularize("posts")             #=> "post"
174 |    *   inflector.singularize("octopi")            #=> "octopus"
175 |    *   inflector.singularize("sheep")             #=> "sheep"
176 |    *   inflector.singularize("words")             #=> "word"
177 |    *   inflector.singularize("the blue mailmen")  #=> "the blue mailman"
178 |    *   inflector.singularize("CamelOctopi")       #=> "CamelOctopus"
179 |    * 
180 | * 181 | *

182 | *

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 | *

205 | * Examples: 206 | * 207 | *

208 |    *   inflector.lowerCamelCase("active_record")       #=> "activeRecord"
209 |    *   inflector.lowerCamelCase("first_name")          #=> "firstName"
210 |    *   inflector.lowerCamelCase("name")                #=> "name"
211 |    *   inflector.lowerCamelCase("the-first_name",'-')  #=> "theFirstName"
212 |    * 
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 | *

231 | * Examples: 232 | * 233 | *

234 |    *   inflector.upperCamelCase("active_record")       #=> "SctiveRecord"
235 |    *   inflector.upperCamelCase("first_name")          #=> "FirstName"
236 |    *   inflector.upperCamelCase("name")                #=> "Name"
237 |    *   inflector.lowerCamelCase("the-first_name",'-')  #=> "TheFirstName"
238 |    * 
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 | *

259 | * Examples: 260 | * 261 | *

262 |    *   inflector.camelCase("active_record",false)    #=> "activeRecord"
263 |    *   inflector.camelCase("active_record",true)     #=> "ActiveRecord"
264 |    *   inflector.camelCase("first_name",false)       #=> "firstName"
265 |    *   inflector.camelCase("first_name",true)        #=> "FirstName"
266 |    *   inflector.camelCase("name",false)             #=> "name"
267 |    *   inflector.camelCase("name",true)              #=> "Name"
268 |    * 
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 | *

308 | * Examples: 309 | * 310 | *

311 |    *   inflector.underscore("activeRecord")     #=> "active_record"
312 |    *   inflector.underscore("ActiveRecord")     #=> "active_record"
313 |    *   inflector.underscore("firstName")        #=> "first_name"
314 |    *   inflector.underscore("FirstName")        #=> "first_name"
315 |    *   inflector.underscore("name")             #=> "name"
316 |    *   inflector.underscore("The.firstName")    #=> "the_first_name"
317 |    * 
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 | *

359 | * Examples: 360 | * 361 | *

362 |    *   inflector.humanize("employee_salary")       #=> "Employee salary"
363 |    *   inflector.humanize("author_id")             #=> "Author"
364 |    * 
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 | *

501 | * 502 | * @param input 503 | * @param regex 504 | * @param groupNumberToUppercase 505 | * @return the input string with the appropriate characters converted to upper-case 506 | */ 507 | protected static String replaceAllWithUppercase(String input, 508 | String regex, 509 | int groupNumberToUppercase) { 510 | Pattern underscoreAndDotPattern = Pattern.compile(regex); 511 | Matcher matcher = underscoreAndDotPattern.matcher(input); 512 | StringBuffer sb = new StringBuffer(); 513 | while (matcher.find()) { 514 | matcher.appendReplacement(sb, matcher.group(groupNumberToUppercase).toUpperCase()); 515 | } 516 | matcher.appendTail(sb); 517 | return sb.toString(); 518 | } 519 | 520 | /** 521 | * Completely remove all rules within this inflector. 522 | */ 523 | public void clear() { 524 | this.uncountables.clear(); 525 | this.plurals.clear(); 526 | this.singulars.clear(); 527 | } 528 | 529 | protected void initialize() { 530 | Inflector inflect = this; 531 | inflect.addPluralize("$", "s"); 532 | inflect.addPluralize("s$", "s"); 533 | inflect.addPluralize("(ax|test)is$", "$1es"); 534 | inflect.addPluralize("(octop|vir)us$", "$1i"); 535 | inflect.addPluralize("(octop|vir)i$", "$1i"); // already plural 536 | inflect.addPluralize("(alias|status)$", "$1es"); 537 | inflect.addPluralize("(bu)s$", "$1ses"); 538 | inflect.addPluralize("(buffal|tomat)o$", "$1oes"); 539 | inflect.addPluralize("([ti])um$", "$1a"); 540 | inflect.addPluralize("([ti])a$", "$1a"); // already plural 541 | inflect.addPluralize("sis$", "ses"); 542 | inflect.addPluralize("(?:([^f])fe|([lr])f)$", "$1$2ves"); 543 | inflect.addPluralize("(hive)$", "$1s"); 544 | inflect.addPluralize("([^aeiouy]|qu)y$", "$1ies"); 545 | inflect.addPluralize("(x|ch|ss|sh)$", "$1es"); 546 | inflect.addPluralize("(matr|vert|ind)ix|ex$", "$1ices"); 547 | inflect.addPluralize("([m|l])ouse$", "$1ice"); 548 | inflect.addPluralize("([m|l])ice$", "$1ice"); 549 | inflect.addPluralize("^(ox)$", "$1en"); 550 | inflect.addPluralize("(quiz)$", "$1zes"); 551 | // Need to check for the following words that are already pluralized: 552 | inflect.addPluralize("(people|men|children|sexes|moves|stadiums)$", "$1"); // irregulars 553 | inflect.addPluralize("(oxen|octopi|viri|aliases|quizzes)$", "$1"); // special rules 554 | 555 | inflect.addSingularize("s$", ""); 556 | inflect.addSingularize("(s|si|u)s$", "$1s"); // '-us' and '-ss' are already singular 557 | inflect.addSingularize("(n)ews$", "$1ews"); 558 | inflect.addSingularize("([ti])a$", "$1um"); 559 | inflect.addSingularize("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$", "$1$2sis"); 560 | inflect.addSingularize("(^analy)ses$", "$1sis"); 561 | inflect.addSingularize("(^analy)sis$", "$1sis"); // already singular, but ends in 's' 562 | inflect.addSingularize("([^f])ves$", "$1fe"); 563 | inflect.addSingularize("(hive)s$", "$1"); 564 | inflect.addSingularize("(tive)s$", "$1"); 565 | inflect.addSingularize("([lr])ves$", "$1f"); 566 | inflect.addSingularize("([^aeiouy]|qu)ies$", "$1y"); 567 | inflect.addSingularize("(s)eries$", "$1eries"); 568 | inflect.addSingularize("(m)ovies$", "$1ovie"); 569 | inflect.addSingularize("(x|ch|ss|sh)es$", "$1"); 570 | inflect.addSingularize("([m|l])ice$", "$1ouse"); 571 | inflect.addSingularize("(bus)es$", "$1"); 572 | inflect.addSingularize("(o)es$", "$1"); 573 | inflect.addSingularize("(shoe)s$", "$1"); 574 | inflect.addSingularize("(cris|ax|test)is$", "$1is"); // already singular, but ends in 's' 575 | inflect.addSingularize("(cris|ax|test)es$", "$1is"); 576 | inflect.addSingularize("(octop|vir)i$", "$1us"); 577 | inflect.addSingularize("(octop|vir)us$", "$1us"); // already singular, but ends in 's' 578 | inflect.addSingularize("(alias|status)es$", "$1"); 579 | inflect.addSingularize("(alias|status)$", "$1"); // already singular, but ends in 's' 580 | inflect.addSingularize("^(ox)en", "$1"); 581 | inflect.addSingularize("(vert|ind)ices$", "$1ex"); 582 | inflect.addSingularize("(matr)ices$", "$1ix"); 583 | inflect.addSingularize("(quiz)zes$", "$1"); 584 | 585 | inflect.addIrregular("person", "people"); 586 | inflect.addIrregular("man", "men"); 587 | inflect.addIrregular("child", "children"); 588 | inflect.addIrregular("sex", "sexes"); 589 | inflect.addIrregular("move", "moves"); 590 | inflect.addIrregular("stadium", "stadiums"); 591 | 592 | inflect.addUncountable("equipment", "information", "rice", "money", "species", "series", "fish", "sheep"); 593 | } 594 | 595 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | liwey.Json2PojoWithLombok 3 | json2pojo with Lombok 4 | 1.1 5 | Leon 6 | 8 | Convert JSON to POJOs, with lombok and gson/jackson annotations.
9 | source code.
10 | input.jpg
11 | config.jpg
12 | 13 | ]]>
14 | 15 | 1.1 - Support @Accessors and @SuppressWarnings. Support i18n. 17 |
  • 1.0 - Support lombok annotations.
  • 18 | ]]> 19 |
    20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 |
    -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin_zh.xml: -------------------------------------------------------------------------------- 1 | 2 | liwey.Json2PojoWithLombok 3 | json2pojo with lombok 4 | 1.1 5 | Leon 6 | 8 | 从JSON生成Java实体类(POJO),并添加lombok与gson/jackson标注。
    9 | 源代码.
    10 | 11 | ]]>
    12 | 13 | 1.1 - 添加标注:@Accessors、@SuppressWarnings。 15 |
  • 1.0 - 支持lombok标注。
  • 16 | ]]> 17 |
    18 | 19 | 20 | 23 | 24 | 25 | 26 | 27 |
    -------------------------------------------------------------------------------- /src/main/resources/bundles_en.properties: -------------------------------------------------------------------------------- 1 | ok=OK 2 | suppress.warnings=Suppress Warnings 3 | field.name.annotate=When generated field name is different, annotate 4 | general=General 5 | result=Result 6 | class.name=Class Name 7 | title=Convert JSON to POJO 8 | settings=Settings 9 | generate.result=JSON has been converted to {0} classes in package "{1}". 10 | success=Success. 11 | generate=Generate 12 | cancel=Cancel 13 | use.primitive=Use primitive data types(boolean, long, double). 14 | generating=Generating class "{0}"... -------------------------------------------------------------------------------- /src/main/resources/bundles_zh.properties: -------------------------------------------------------------------------------- 1 | class.name=Class\u540D\u5B57 2 | title=\u6839\u636EJSON\u751F\u6210POJO 3 | settings=\u8BBE\u7F6E 4 | generate.result=\u5DF2\u751F\u6210{0}\u4E2A\u7C7B\u5728\u5305{1}\u4E2D\u3002 5 | success=\u5B8C\u6210\u3002 6 | generate=\u751F\u6210 7 | cancel=\u53D6\u6D88 8 | use.primitive=\u4F7F\u7528\u539F\u751F\u7C7B\u578B\uFF08boolean, long, double\uFF09\u3002 9 | result=\u7ED3\u679C 10 | general=\u901A\u7528 11 | field.name.annotate=\u5F53\u751F\u6210\u5B57\u6BB5\u540D\u4E0EJSON\u540D\u5B57\u4E0D\u4E00\u81F4\u65F6\uFF0C\u6807\u6CE8 12 | suppress.warnings=\u9690\u533F\u8B66\u544A 13 | ok=\u786E\u5B9A 14 | generating=\u6B63\u5728\u751F\u6210\u7C7B{0}... -------------------------------------------------------------------------------- /src/main/resources/icons/json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenglian/json2pojo/75ba8699add62a40387a1fb69a88d1f8872d3c00/src/main/resources/icons/json.png -------------------------------------------------------------------------------- /src/test/java/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "18092f4d-c54a-42c6-98e0-438aeef9f1fa", 3 | "runId" : "16a3734b-0418-4231-ae5f-c06ee8638f97", 4 | "name" : "Opportunity_UHIgcccj_MilestoneHistory", 5 | "timestamp" : "2018-11-10T11:18:20.005Z", 6 | "batch.id" : 55, 7 | "num_input_rows" : 191, 8 | "input-rows-per-second" : 11.019442681590032, 9 | "processedRowsPerSecond" : 0.5130698500278025, 10 | "durationMs" : { 11 | "addBatch" : 371801, 12 | "getBatch" : 10, 13 | "getOffset" : 294, 14 | "queryPlanning" : 73, 15 | "triggerExecution" : 372269, 16 | "walCommit" : 86 17 | }, 18 | "stateOperators" : [ { 19 | "numRowsTotal": 0, 20 | "numRowsUpdated": 0, 21 | "memoryUsedBytes": 0 22 | }], 23 | "sources" : [ { 24 | "description" : "KafkaSource[Assign[insight-tenant1-0]]", 25 | "startOffset" : { 26 | "insight-tenant1" : { 27 | "p0" : 234571 28 | } 29 | }, 30 | "endOffset" : { 31 | "insight-tenant1" : { 32 | "p0" : 234762 33 | } 34 | }, 35 | "numInputRows" : 191, 36 | "inputRowsPerSecond" : 11.019442681590032, 37 | "processedRowsPerSecond" : 0.5130698500278025 38 | } ], 39 | "sink" : { 40 | "description" : "ForeachSink" 41 | } 42 | } -------------------------------------------------------------------------------- /src/test/java/liwey/json2pojo/GeneratorTest.java: -------------------------------------------------------------------------------- 1 | package liwey.json2pojo; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.Objects; 10 | 11 | import javax.tools.*; 12 | 13 | import org.junit.Assert; 14 | 15 | import junit.framework.TestCase; 16 | 17 | public class GeneratorTest extends TestCase { 18 | public void testFormatClassName() { 19 | assertEquals("Test", Generator.formatClassName("test")); 20 | assertEquals("TestCamelCase", Generator.formatClassName("testCamelCase")); 21 | assertEquals("TestWithUnderscores", Generator.formatClassName("test_with_underscores")); 22 | assertEquals("TestWithHyphens", Generator.formatClassName("test-with-hyphens")); 23 | assertEquals("TestWithDots", Generator.formatClassName("test.with.dots")); 24 | assertEquals("AbstractTest", Generator.formatClassName("abstractTest")); 25 | assertEquals("Test", Generator.formatClassName("1Test")); 26 | assertEquals("InvalidChars", Generator.formatClassName("Invalid@$%@#$^&#%@Chars")); 27 | } 28 | 29 | public void testExamples() throws Exception { 30 | String src = System.getProperty("user.dir") + "/src/test/java"; 31 | File jsonFile = new File(src + "/example.json"); 32 | String json = new String(Files.readAllBytes(jsonFile.toPath())); 33 | String dest = System.getProperty("user.dir") + "/build/classes/java/main"; 34 | String packageName = "example.spark"; 35 | Generator generator = new Generator(packageName, src, null); 36 | int n = generator.generateFromJson("SparkProgress", json); 37 | Assert.assertEquals(8, n); 38 | } 39 | 40 | private boolean compile(String packageName, String src, String dest) throws IOException { 41 | List classes = new ArrayList<>(); 42 | for (File file : Objects.requireNonNull(new File(src + "/" + packageName.replace('.', '/')).listFiles())) { 43 | classes.add(file.getAbsolutePath()); 44 | } 45 | JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 46 | DiagnosticCollector diagnostics = new DiagnosticCollector(); 47 | StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null); 48 | JavaFileManager.Location oLocation = StandardLocation.CLASS_OUTPUT; 49 | fileManager.setLocation(oLocation, Arrays.asList(new File(dest))); 50 | Iterable compilationUnits = fileManager.getJavaFileObjectsFromStrings(classes); 51 | JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits); 52 | boolean result = task.call(); 53 | fileManager.close(); 54 | return result; 55 | } 56 | } --------------------------------------------------------------------------------