added support for models, partial for ops

This commit is contained in:
Tony Tam
2014-09-01 13:31:01 -07:00
parent d1daf3a0e6
commit 812520e29f
15 changed files with 605 additions and 163 deletions

View File

@@ -1,5 +1,6 @@
package com.wordnik.swagger.codegen;
import com.wordnik.swagger.codegen.languages.*;
import com.wordnik.swagger.models.Swagger;
import com.wordnik.swagger.util.Json;
@@ -7,15 +8,28 @@ import java.io.File;
public class Codegen extends DefaultGenerator {
public static void main(String[] args) {
JavaCodegen config = new JavaCodegen();
config.setTemplateDir("src/main/resources/Java");
CodegenConfig config = null;
if(args != null && args.length > 0) {
String lang = args[0];
if("java".equals(lang)) {
JavaClientCodegen javaConfig = new JavaClientCodegen();
javaConfig.setTemplateDir("src/main/resources/Java");
config = javaConfig;
}
else if ("objc".equals(lang)) {
ObjcClientCodegen objcConfig = new ObjcClientCodegen();
objcConfig.setTemplateDir("src/main/resources/objc");
config = objcConfig;
}
}
try{
Swagger swagger = (Swagger) Json.mapper()
.readValue(new File("swagger.json"), Swagger.class);
new Codegen()
.config(new JavaCodegen())
.config(config)
.generate(swagger);
}
catch (Exception e) {

View File

@@ -1,20 +1,30 @@
package com.wordnik.swagger.codegen;
import com.wordnik.swagger.models.ModelImpl;
import com.wordnik.swagger.models.*;
import java.util.*;
public interface CodegenConfig {
CodegenModel fromModel(String name, ModelImpl model);
String modelPackage();
Set<String> defaultIncludes();
Map<String, String> typeMapping();
Set<String> reservedWords();
Map<String, String> importMapping();
String apiPackage();
String apiFileFolder();
String fileSuffix();
String templateDir();
Map<String, String> modelTemplateFiles();
String toModelFilename(String name);
String modelFileFolder();
String modelPackage();
String toApiName(String name);
String toModelName(String name);
Set<String> reservedWords();
CodegenModel fromModel(String name, ModelImpl model);
CodegenOperation fromOperation(String resourcePath, String httpMethod, Operation operation);
Set<String> defaultIncludes();
Map<String, String> typeMapping();
Map<String, String> importMapping();
Map<String, String> apiTemplateFiles();
Map<String, String> modelTemplateFiles();
String toApiFilename(String name);
String toModelFilename(String name);
String toModelImport(String name);
}

View File

@@ -9,4 +9,5 @@ class CodegenModel {
public String name, classname, description;
public String defaultValue;
public List<CodegenProperty> vars = new ArrayList<CodegenProperty>();
public Set<String> imports = new HashSet<String>();
}

View File

@@ -0,0 +1,15 @@
package com.wordnik.swagger.codegen;
import com.wordnik.swagger.models.*;
import com.wordnik.swagger.models.properties.*;
import java.util.*;
public class CodegenOperation {
public String path, operationId,
returnType;
// legacy support
public String nickname;
}

View File

@@ -6,6 +6,11 @@ import com.wordnik.swagger.models.properties.*;
import java.util.*;
public class CodegenProperty {
public String getter, setter, description, datatype, name, min, max, defaultValue;
public String baseName, complexType, getter, setter, description, datatype, name,
min, max, defaultValue;
public Double minimum, maximum, exclusiveMinimum, exclusiveMaximum;
public Boolean hasMore = null, required = null;
public boolean isPrimitiveType, isContainer, isNotContainer;
public List<String> _enum;
public Map<String, Object> allowableValues;
}
}

View File

@@ -1,5 +1,6 @@
package com.wordnik.swagger.codegen;
import com.wordnik.swagger.util.Json;
import com.wordnik.swagger.models.*;
import com.wordnik.swagger.models.properties.*;
@@ -7,15 +8,16 @@ import java.util.*;
import java.io.File;
public class DefaultCodegen {
String outputFolder = "";
Set<String> defaultIncludes;
Map<String, String> typeMapping;
Set<String> reservedWords;
Map<String, String> importMapping;
String modelPackage = "", apiPackage, fileSuffix;
Map<String, String> apiTemplateFiles = new HashMap<String, String>();
Map<String, String> modelTemplateFiles = new HashMap<String, String>();
String templateDir;
protected String outputFolder = "";
protected Set<String> defaultIncludes = new HashSet<String>();
protected Map<String, String> typeMapping = new HashMap<String, String>();
protected Set<String> reservedWords = new HashSet<String>();
protected Set<String> languageSpecificPrimitives = new HashSet<String>();
protected Map<String, String> importMapping = new HashMap<String, String>();
protected String modelPackage = "", apiPackage = "", fileSuffix;
protected Map<String, String> apiTemplateFiles = new HashMap<String, String>();
protected Map<String, String> modelTemplateFiles = new HashMap<String, String>();
protected String templateDir;
public Set<String> defaultIncludes() {
return defaultIncludes;
@@ -26,6 +28,9 @@ public class DefaultCodegen {
public Set<String> reservedWords() {
return reservedWords;
}
public Set<String> languageSpecificPrimitives() {
return languageSpecificPrimitives;
}
public Map<String, String> importMapping() {
return importMapping;
}
@@ -41,9 +46,15 @@ public class DefaultCodegen {
public String templateDir() {
return templateDir;
}
public Map<String, String> apiTemplateFiles() {
return apiTemplateFiles;
}
public Map<String, String> modelTemplateFiles() {
return modelTemplateFiles;
}
public String apiFileFolder() {
return outputFolder + File.separator + apiPackage().replaceAll("\\.", File.separator);
}
public String modelFileFolder() {
return outputFolder + File.separator + modelPackage().replaceAll("\\.", File.separator);
}
@@ -52,26 +63,38 @@ public class DefaultCodegen {
this.templateDir = templateDir;
}
public String toApiFilename(String name) {
return initialCaps(name);
}
public String toModelFilename(String name) {
return name;
}
public String toVarName(String name) {
return name;
}
public String toModelImport(String name) {
return modelPackage() + "." + name;
}
public DefaultCodegen() {
defaultIncludes = new HashSet<String>(
Arrays.asList("double",
"int",
"long",
"short",
"char",
"float",
"String",
"boolean",
"Boolean",
"Double",
"Integer",
"Long",
"Float")
);
Arrays.asList("double",
"int",
"long",
"short",
"char",
"float",
"String",
"boolean",
"Boolean",
"Double",
"Integer",
"Long",
"Float")
);
typeMapping = new HashMap<String, String>();
typeMapping.put("Array", "List");
@@ -82,6 +105,7 @@ public class DefaultCodegen {
typeMapping.put("int", "Integer");
typeMapping.put("float", "Float");
typeMapping.put("number", "BigDecimal");
typeMapping.put("DateTime", "Date");
typeMapping.put("long", "Long");
typeMapping.put("short", "Short");
typeMapping.put("char", "String");
@@ -137,13 +161,20 @@ public class DefaultCodegen {
return "null";
else if (p instanceof ArrayProperty) {
ArrayProperty ap = (ArrayProperty) p;
String inner = toDeclaredType(ap.getItems());
return "ArrayList<" + inner + ">() ";
String inner = getSwaggerType(ap.getItems());
return "new ArrayList<" + inner + ">() ";
}
else {
System.out.println("unhandled property default value");
Json.prettyPrint(p);
return "null";
}
return null;
}
public String toDeclaredType(Property p) {
/**
* returns the swagger type for the property
**/
public String getSwaggerType(Property p) {
String datatype = null;
if(p instanceof StringProperty)
datatype = "string";
@@ -152,7 +183,7 @@ public class DefaultCodegen {
else if(p instanceof DateProperty)
datatype = "date";
else if(p instanceof DateTimeProperty)
datatype = "dateTime";
datatype = "DateTime";
else if (p instanceof DoubleProperty)
datatype = "double";
else if (p instanceof FloatProperty)
@@ -163,6 +194,12 @@ public class DefaultCodegen {
datatype = "long";
else if (p instanceof MapProperty)
datatype = "map";
else if (p instanceof RefProperty) {
RefProperty r = (RefProperty)p;
datatype = r.get$ref();
if(datatype.indexOf("#/definitions/") == 0)
datatype = datatype.substring("#/definitions/".length());
}
else {
if(p != null)
datatype = p.getType();
@@ -170,15 +207,48 @@ public class DefaultCodegen {
return datatype;
}
public String initialCaps(String name) {
return Character.toUpperCase(name.charAt(0)) + name.substring(1);
}
public String getTypeDeclaration(Property p) {
String swaggerType = getSwaggerType(p);
if(typeMapping.containsKey(swaggerType))
return typeMapping.get(swaggerType);
return swaggerType;
}
public String toApiName(String name) {
return initialCaps(name);
}
public String toModelName(String name) {
return initialCaps(name);
}
public CodegenModel fromModel(String name, ModelImpl model) {
CodegenModel m = new CodegenModel();
m.name = name;
m.description = model.getDescription();
m.classname = name;
m.classname = toModelName(name);
int count = 0;
for(String key: model.getProperties().keySet()) {
Property prop = model.getProperties().get(key);
m.vars.add(fromProperty(key, prop));
if(prop == null) {
System.out.println("null property for " + key);
Json.prettyPrint(model.getProperties());
}
else {
CodegenProperty cp = fromProperty(key, prop);
if(cp.complexType != null && !defaultIncludes.contains(cp.complexType)) {
m.imports.add(cp.complexType);
}
m.vars.add(cp);
count += 1;
if(count != model.getProperties().keySet().size())
cp.hasMore = new Boolean(true);
}
}
return m;
}
@@ -186,19 +256,92 @@ public class DefaultCodegen {
public CodegenProperty fromProperty(String name, Property p) {
CodegenProperty property = new CodegenProperty();
property.name = name;
property.name = toVarName(name);
property.baseName = name;
property.description = p.getDescription();
property.getter = "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
property.setter = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
property.defaultValue = toDefaultValue(p);
String datatype = toDeclaredType(p);
property.required = p.getRequired();
if(typeMapping.containsKey(datatype)) {
property.datatype = typeMapping.get(datatype);
String type = getSwaggerType(p);
if(p instanceof AbstractNumericProperty) {
AbstractNumericProperty np = (AbstractNumericProperty) p;
property.minimum = np.getMinimum();
property.maximum = np.getMaximum();
property.exclusiveMinimum = np.getExclusiveMinimum();
property.exclusiveMaximum = np.getExclusiveMaximum();
// legacy support
Map<String, Object> allowableValues = new HashMap<String, Object>();
if(np.getMinimum() != null)
allowableValues.put("min", np.getMinimum());
if(np.getMaximum() != null)
allowableValues.put("max", np.getMaximum());
property.allowableValues = allowableValues;
}
if(p instanceof StringProperty) {
StringProperty sp = (StringProperty) p;
if(sp.getEnum() != null) {
List<String> _enum = sp.getEnum();
property._enum = _enum;
// legacy support
Map<String, Object> allowableValues = new HashMap<String, Object>();
allowableValues.put("values", _enum);
property.allowableValues = allowableValues;
}
}
property.datatype = getTypeDeclaration(p);
if(languageSpecificPrimitives().contains(type)) {
property.isPrimitiveType = true;
}
else
property.datatype = datatype;
property.complexType = type;
if(p instanceof ArrayProperty)
property.isContainer = true;
else
property.isNotContainer = true;
return property;
}
public CodegenOperation fromOperation(String path, String httpMethod, Operation operation){
CodegenOperation op = new CodegenOperation();
String operationId = operation.getOperationId();
if(operationId == null)
operationId = "fixme";
op.path = path;
op.operationId = operationId;
Response methodResponse = null;
if(operation.getResponses() != null) {
for(String responseCode: operation.getResponses().keySet()) {
Response response = operation.getResponses().get(responseCode);
if("200".equals(responseCode)) {
methodResponse = response;
}
}
if(methodResponse == null && operation.getResponses().keySet().contains("default"))
methodResponse = operation.getResponses().get("default");
}
if(methodResponse != null && methodResponse.getSchema() != null) {
CodegenProperty responseModel = fromProperty("response", methodResponse.getSchema());
Json.prettyPrint(responseModel);
op.returnType = responseModel.datatype;
}
// legacy support
op.nickname = operationId;
return op;
}
}

View File

@@ -20,8 +20,8 @@ public class DefaultGenerator implements Generator {
public void generate(Swagger swagger) {
try {
// models
Map<String, Model> definitions = swagger.getDefinitions();
for(String name: definitions.keySet()) {
Model model = definitions.get(name);
Map<String, Model> modelMap = new HashMap<String, Model>();
@@ -39,12 +39,77 @@ public class DefaultGenerator implements Generator {
writeToFile(filename, tmpl.execute(models));
}
}
// apis
Map<String, List<CodegenOperation>> paths = groupPaths(swagger.getPaths());
for(String tag: paths.keySet()) {
List<CodegenOperation> ops = paths.get(tag);
Object tagObject = processOperations(config, tag, ops);
Json.prettyPrint(tagObject);
for(String templateName: config.apiTemplateFiles().keySet()) {
String suffix = config.apiTemplateFiles().get(templateName);
String filename = config.apiFileFolder() + File.separator + config.toApiFilename(tag) + suffix;
String template = readTemplate(config.templateDir() + File.separator + templateName);
Template tmpl = Mustache.compiler()
.defaultValue("")
.compile(template);
writeToFile(filename, tmpl.execute(tagObject));
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
public void processOperation(String tag, String resourcePath, String httpMethod, Operation operation, Map<String, List<CodegenOperation>> operations) {
if(tag == null)
tag = "default";
List<CodegenOperation> opList = operations.get(tag);
if(opList == null) {
opList = new ArrayList<CodegenOperation>();
operations.put(tag, opList);
}
CodegenOperation co = config.fromOperation(resourcePath, httpMethod, operation);
opList.add(co);
}
public Map<String, List<CodegenOperation>> groupPaths(Map<String, Path> paths) {
// group by tag, create a Default grouping if none
Map<String, List<CodegenOperation>> ops = new HashMap<String, List<CodegenOperation>>();
List<String> tags = null;
for(String resourcePath: paths.keySet()) {
Path path = paths.get(resourcePath);
Operation get = path.getGet();
if(get != null) {
tags = get.getTags();
if(tags != null && tags.size() > 0) {
for(String tag: tags) {
processOperation(tag, resourcePath, "get", get, ops);
}
}
else {
processOperation(null, resourcePath, "get", get, ops);
}
}
// List<CodegenOperation> ops = ops
Operation put = path.getPut();
Operation post = path.getPost();
Operation delete = path.getDelete();
Operation patch = path.getPatch();
Operation options = path.getOptions();
}
Json.prettyPrint(ops);
return ops;
}
public File writeToFile(String filename, String contents) throws IOException {
System.out.println("writing file " + filename);
File output = new File(filename);
@@ -77,11 +142,21 @@ public class DefaultGenerator implements Generator {
throw new RuntimeException("can't load template " + name);
}
public Map<String, Object> processOperations(CodegenConfig config, String tag, List<CodegenOperation> ops) {
Map<String, Object> operations = new HashMap<String, Object>();
Map<String, Object> objs = new HashMap<String, Object>();
objs.put("classname", config.toApiName(tag));
objs.put("operation", ops);
operations.put("operations", objs);
return operations;
}
public Map<String, Object> processModels(CodegenConfig config, Map<String, Model> definitions) {
Map<String, Object> objs = new HashMap<String, Object>();
objs.put("package", config.modelPackage());
List<Object> models = new ArrayList<Object>();
List<Object> model = new ArrayList<Object>();
Set<String> allImports = new HashSet<String>();
for(String key: definitions.keySet()) {
Model mm = definitions.get(key);
if(mm instanceof ModelImpl) {
@@ -89,9 +164,24 @@ public class DefaultGenerator implements Generator {
Map<String, Object> mo = new HashMap<String, Object>();
mo.put("model", cm);
models.add(mo);
allImports.addAll(cm.imports);
}
}
objs.put("models", models);
List<Map<String, String>> imports = new ArrayList<Map<String, String>>();
for(String i: allImports) {
Map<String, String> im = new HashMap<String, String>();
String m = config.importMapping().get(i);
if(m == null)
m = config.toModelImport(i);
if(m != null) {
im.put("import", m);
imports.add(im);
}
}
objs.put("imports", imports);
return objs;
}
}

View File

@@ -1,14 +0,0 @@
package com.wordnik.swagger.codegen;
public class JavaCodegen extends DefaultCodegen implements CodegenConfig {
public JavaCodegen() {
super();
outputFolder = "generated-code/java";
modelTemplateFiles.put("model.mustache", ".java");
apiTemplateFiles.put("api.mustache", ".java");
templateDir = "Java";
modelPackage = "com.wordnik.model";
}
}

View File

@@ -1,13 +0,0 @@
package com.wordnik.swagger.codegen;
public class Person {
public final String name;
public Person (String name, int age) {
this.name = name;
_age = age;
}
public int getAge () {
return _age;
}
protected int _age;
}

View File

@@ -0,0 +1,61 @@
package com.wordnik.swagger.codegen.languages;
import com.wordnik.swagger.codegen.*;
import com.wordnik.swagger.models.properties.*;
import java.util.*;
public class JavaClientCodegen extends DefaultCodegen implements CodegenConfig {
public JavaClientCodegen() {
super();
outputFolder = "generated-code/java";
modelTemplateFiles.put("model.mustache", ".java");
apiTemplateFiles.put("api.mustache", ".java");
templateDir = "Java";
apiPackage = "com.wordnik.api";
modelPackage = "com.wordnik.model";
languageSpecificPrimitives = new HashSet<String>(
Arrays.asList(
"String",
"boolean",
"Boolean",
"Double",
"Integer",
"Long",
"Float")
);
}
@Override
public String toModelImport(String name) {
return null;
}
@Override
public String getTypeDeclaration(Property p) {
if(p instanceof ArrayProperty) {
ArrayProperty ap = (ArrayProperty) p;
Property inner = ap.getItems();
return getSwaggerType(p) + "<" + getTypeDeclaration(inner) + ">";
}
else if (p instanceof MapProperty) {
throw new RuntimeException("not supported yet");
}
return super.getTypeDeclaration(p);
}
@Override
public String getSwaggerType(Property p) {
String swaggerType = super.getSwaggerType(p);
String type = null;
if(typeMapping.containsKey(swaggerType)) {
type = typeMapping.get(swaggerType);
if(languageSpecificPrimitives.contains(type))
return toModelName(type);
}
else
type = swaggerType;
return toModelName(type);
}
}

View File

@@ -0,0 +1,136 @@
package com.wordnik.swagger.codegen.languages;
import com.wordnik.swagger.util.Json;
import com.wordnik.swagger.codegen.*;
import com.wordnik.swagger.models.properties.*;
import java.util.*;
import java.io.File;
public class ObjcClientCodegen extends DefaultCodegen implements CodegenConfig {
protected Set<String> foundationClasses = new HashSet<String>();
public ObjcClientCodegen() {
super();
outputFolder = "generated-code/objc";
modelTemplateFiles.put("model-header.mustache", ".h");
modelTemplateFiles.put("model-body.mustache", ".m");
apiTemplateFiles.put("api-header.mustache", ".h");
apiTemplateFiles.put("api-body.mustache", ".m");
templateDir = "objc";
modelPackage = "com.wordnik.model";
defaultIncludes = new HashSet<String>(
Arrays.asList("bool",
"int",
"NSString",
"NSObject",
"NSArray",
"NSNumber")
);
languageSpecificPrimitives = new HashSet<String>(
Arrays.asList(
"NSNumber",
"NSString",
"NSObject",
"bool")
);
reservedWords = new HashSet<String>(
Arrays.asList(
"void", "char", "short", "int", "void", "char", "short", "int",
"long", "float", "double", "signed", "unsigned", "id", "const",
"volatile", "in", "out", "inout", "bycopy", "byref", "oneway",
"self", "super"
));
typeMapping = new HashMap<String, String>();
typeMapping.put("enum", "NSString");
typeMapping.put("date", "SWGDate");
typeMapping.put("dateTime", "SWGDate");
// typeMapping.put("Date", "SWGDate");
typeMapping.put("boolean", "NSNumber");
typeMapping.put("string", "NSString");
typeMapping.put("integer", "NSNumber");
typeMapping.put("int", "NSNumber");
typeMapping.put("float", "NSNumber");
typeMapping.put("long", "NSNumber");
typeMapping.put("double", "NSNumber");
typeMapping.put("Array", "NSArray");
// typeMapping.put("array", "NSArray");
typeMapping.put("List", "NSArray");
typeMapping.put("object", "NSObject");
importMapping = new HashMap<String, String> ();
importMapping.put("Date", "SWGDate");
foundationClasses = new HashSet<String> (
Arrays.asList(
"NSNumber",
"NSObject",
"NSString")
);
}
@Override
public String getSwaggerType(Property p) {
String swaggerType = super.getSwaggerType(p);
String type = null;
System.out.println("checking type mapping for " + swaggerType);
if(typeMapping.containsKey(swaggerType)) {
type = typeMapping.get(swaggerType);
if(languageSpecificPrimitives.contains(type) && !foundationClasses.contains(type))
return toModelName(type);
}
else
type = swaggerType;
return toModelName(type);
}
@Override
public String getTypeDeclaration(Property p) {
String swaggerType = getSwaggerType(p);
if(languageSpecificPrimitives.contains(swaggerType) && !foundationClasses.contains(swaggerType))
return toModelName(swaggerType);
else
return swaggerType + "*";
}
@Override
public String toModelName(String type) {
if(typeMapping.keySet().contains(type) ||
foundationClasses.contains(type) ||
importMapping.values().contains(type) ||
defaultIncludes.contains(type) ||
languageSpecificPrimitives.contains(type)) {
return Character.toUpperCase(type.charAt(0)) + type.substring(1);
}
else {
return "SWG" + Character.toUpperCase(type.charAt(0)) + type.substring(1);
}
}
@Override
public String modelFileFolder() {
return outputFolder + File.separator + "models";
}
@Override
public String toModelFilename(String name) {
return "SWG" + name;
}
@Override
public String toVarName(String name) {
String paramName = name.replaceAll("[^a-zA-Z0-9_]","");
if(paramName.startsWith("new") || reservedWords.contains(paramName)) {
return escapeReservedWord(paramName);
}
else
return paramName;
}
public String escapeReservedWord(String name) {
return "_" + name;
}
}