added reserved word translation support

This commit is contained in:
Tony Tam
2011-09-28 17:37:30 -07:00
parent 2cc57fc30c
commit 25f7ec2bd5
12 changed files with 341 additions and 84 deletions

View File

@@ -20,17 +20,11 @@ import java.util.ArrayList;
import java.util.List;
public class FieldDefinition {
private String returnType;
private String name;
private String initialization;
private List<String> importDefinitions = new ArrayList<String>();
private String collectionItemType;
private String collectionItemName;
private boolean hasListResponse;
@@ -80,7 +74,7 @@ public class FieldDefinition {
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}

View File

@@ -17,10 +17,10 @@ package com.wordnik.swagger.codegen;
import com.wordnik.swagger.codegen.api.SwaggerResourceDocReader;
import com.wordnik.swagger.codegen.config.*;
import com.wordnik.swagger.codegen.config.ApiConfiguration;
import com.wordnik.swagger.codegen.config.common.CamelCaseNamingPolicyProvider;
import com.wordnik.swagger.codegen.exception.CodeGenerationException;
import com.wordnik.swagger.codegen.resource.*;
import org.antlr.stringtemplate.StringTemplate;
import org.antlr.stringtemplate.StringTemplateGroup;
import org.codehaus.jackson.map.DeserializationConfig;
@@ -39,7 +39,6 @@ import java.util.List;
* Time: 6:59 PM
*/
public class LibraryCodeGenerator {
private static String VERSION_OBJECT_TEMPLATE = "VersionChecker";
private static String MODEL_OBJECT_TEMPLATE = "ModelObject";
private static String API_OBJECT_TEMPLATE = "ResourceObject";
@@ -49,6 +48,7 @@ public class LibraryCodeGenerator {
protected static final String PACKAGE_NAME = "packageName";
protected ApiConfiguration config = null;
protected LanguageConfiguration languageConfig = null;
protected ReservedWordMapper reservedWordMapper = new DefaultReservedWordMapper();
private SwaggerResourceDocReader apiMarshaller;
protected DataTypeMappingProvider dataTypeMappingProvider;
@@ -64,7 +64,6 @@ public class LibraryCodeGenerator {
protected void initializeWithConfigPath(String configPath){
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
final File configFile = new File(configPath);
this.setApiConfig(readApiConfiguration(configPath, mapper, configFile));
this.setCodeGenRulesProvider(readRulesProviderConfig(configPath, mapper, configFile));
@@ -102,6 +101,7 @@ public class LibraryCodeGenerator {
apiMarshaller = new SwaggerResourceDocReader(this.config, this.getDataTypeMappingProvider(), this.getNameGenerator());
//read resources and get their documentation
List<Resource> resources = apiMarshaller.readResourceDocumentation();
preprocess(resources);
StringTemplateGroup aTemplateGroup = new StringTemplateGroup("templates", languageConfig.getTemplateLocation());
if(resources.size() > 0) {
generateVersionHelper(resources.get(0).getApiVersion(), aTemplateGroup);
@@ -120,6 +120,21 @@ public class LibraryCodeGenerator {
}
/**
* prepares the model for template generation
* @param resources
*/
protected void preprocess(List<Resource> resources) {
for(Resource resource: resources) {
for(Model model : resource.getModels()){
// apply keyword mapping
for(ModelField modelField : model.getFields()){
modelField.setName(reservedWordMapper.translate(modelField.getName()));
}
}
}
}
/**
* Generates version file based on the version number received from the doc calls. This version file is used
* while making the API calls to make sure Client and back end are compatible.
* @param version
@@ -170,7 +185,7 @@ public class LibraryCodeGenerator {
}
/**
* Generates assembler classes if the API returns more than one objects.
* Generates assembler classes if the API returns more than one object.
* @param resources
* @param templateGroup
*/
@@ -189,6 +204,7 @@ public class LibraryCodeGenerator {
List<String> imports = new ArrayList<String>();
imports.addAll(this.config.getDefaultModelImports());
for(ModelField param : model.getFields()){
param.setName(reservedWordMapper.translate(param.getName()));
for(String importDef : param.getFieldDefinition(this.getDataTypeMappingProvider(), config, nameGenerator).getImportDefinitions()){
if(!imports.contains(importDef)){
imports.add(importDef);
@@ -340,23 +356,26 @@ public class LibraryCodeGenerator {
methods = resource.generateMethods(resource, dataTypeMappingProvider, nameGenerator);
StringTemplate template = templateGroup.getInstanceOf(API_OBJECT_TEMPLATE);
String className = resource.generateClassName(nameGenerator);
List<ResourceMethod> filteredMethods = new ArrayList<ResourceMethod>();
for(ResourceMethod method:methods){
if(!this.getCodeGenRulesProvider().isMethodIgnored(className, method.getName())){
filteredMethods.add(method);
}
}
template.setAttribute("imports", imports);
template.setAttribute(PACKAGE_NAME, config.getApiPackageName());
template.setAttribute("annotationPackageName", languageConfig.getAnnotationPackageName());
template.setAttribute("modelPackageName", config.getModelPackageName());
template.setAttribute("exceptionPackageName", languageConfig.getExceptionPackageName());
template.setAttribute("resource", className);
template.setAttribute("methods", filteredMethods);
template.setAttribute("extends", config.getServiceBaseClass(className));
File aFile = new File(languageConfig.getResourceClassLocation()+ resource.generateClassName(nameGenerator) +languageConfig.getClassFileExtension());
writeFile(aFile, template.toString(), "API Classes");
if(className != null){
List<ResourceMethod> filteredMethods = new ArrayList<ResourceMethod>();
for(ResourceMethod method:methods){
if(!this.getCodeGenRulesProvider().isMethodIgnored(className, method.getName())){
filteredMethods.add(method);
}
}
template.setAttribute("imports", imports);
template.setAttribute(PACKAGE_NAME, config.getApiPackageName());
template.setAttribute("annotationPackageName", languageConfig.getAnnotationPackageName());
template.setAttribute("modelPackageName", config.getModelPackageName());
template.setAttribute("exceptionPackageName", languageConfig.getExceptionPackageName());
template.setAttribute("resource", className);
template.setAttribute("methods", filteredMethods);
template.setAttribute("extends", config.getServiceBaseClass(className));
File aFile = new File(languageConfig.getResourceClassLocation()+ resource.generateClassName(nameGenerator) +languageConfig.getClassFileExtension());
writeFile(aFile, template.toString(), "API Classes");
}
}catch(RuntimeException t){
System.out.println("Failed generating api class for the resource : " + resource.getResourcePath());
throw t;

View File

@@ -21,44 +21,25 @@ import com.wordnik.swagger.codegen.resource.Model;
import java.util.List;
public class ResourceMethod {
private String title;
private String description;
private List<MethodArgument> arguments;
private List<MethodArgument> queryParameters;
private List<MethodArgument> pathParameters;
//set the original response name, this is used in identifying if the respone is single valued or multi valued
//set the original response name, this is used in identifying if the response is single valued or multi valued
private String returnValueFromOperationJson;
private String returnValue;
private String returnClassName;
private String exceptionDescription;
private List<String> argumentDefinitions;
private List<String> argumentNames;
private String name;
private boolean authToken;
private String resourcePath;
private String methodType;
private boolean postObject;
private Model inputModel;
private Model listWrapperModel;
private boolean hasResponseValue;
public boolean isHasResponseValue(){

View File

@@ -0,0 +1,31 @@
/**
* Copyright 2011 Wordnik, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wordnik.swagger.codegen.config;
/**
* class to translate reserved words to safe variable names
*
* @author tony
*
*/
public class DefaultReservedWordMapper implements ReservedWordMapper {
@Override
public String translate(String input) {
// does nothing
return input;
}
}

View File

@@ -24,25 +24,15 @@ import com.wordnik.swagger.codegen.exception.CodeGenerationException;
* Time: 8:01 AM
*/
public class LanguageConfiguration {
private String classFileExtension;
private String templateLocation;
private String structureLocation;
private String libraryHome;
private String modelClassLocation;
private String resourceClassLocation;
private String exceptionPackageName;
private String annotationPackageName;
private boolean isModelEnumRequired = true;
private boolean isOutputWrapperRequired = false;
public String getClassFileExtension() {
@@ -129,4 +119,17 @@ public class LanguageConfiguration {
public boolean isOutputWrapperRequired() {
return isOutputWrapperRequired;
}
@Override
public String toString() {
return "LanguageConfiguration [classFileExtension="
+ classFileExtension + ", templateLocation=" + templateLocation
+ ", structureLocation=" + structureLocation + ", libraryHome="
+ libraryHome + ", modelClassLocation=" + modelClassLocation
+ ", resourceClassLocation=" + resourceClassLocation
+ ", exceptionPackageName=" + exceptionPackageName
+ ", annotationPackageName=" + annotationPackageName
+ ", isModelEnumRequired=" + isModelEnumRequired
+ ", isOutputWrapperRequired=" + isOutputWrapperRequired + "]";
}
}

View File

@@ -0,0 +1,21 @@
/**
* Copyright 2011 Wordnik, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wordnik.swagger.codegen.config;
public interface ReservedWordMapper {
public String translate(String input);
}

View File

@@ -0,0 +1,198 @@
/**
* Copyright 2011 Wordnik, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wordnik.swagger.codegen.config.common;
import com.wordnik.swagger.codegen.exception.CodeGenerationException;
import com.wordnik.swagger.codegen.config.NamingPolicyProvider;
/**
* User: ramesh
* Date: 5/31/11
* Time: 7:03 AM
*/
public class UnderscoreNamingPolicyProvider implements NamingPolicyProvider {
public static String INPUT_OBJECT_SUFFIX = "Input";
String convertToUnderscoreName(String input){
if(null==input) return null;
return input.replaceAll(
String.format("%s|%s|%s",
"(?<=[A-Z])(?=[A-Z][a-z])",
"(?<=[^A-Z])(?=[A-Z])",
"(?<=[A-Za-z])(?=[^A-Za-z])"),
"_").toLowerCase();
}
/**
* gets the name of class that is responsible for tracking current library version
* @return
*/
public String getVersionCheckerClassName() {
return "version_checker";
}
/**
* Converts the first character of the input into upper case .
* Example: If the input is word, the return value will be Word
* @param input
* @return
*/
public String applyClassNamingPolicy(String input) {
if(input != null && input.length() > 0) {
return convertToUnderscoreName(input);
}else{
throw new CodeGenerationException("Error converting input to first letter caps becuase of null or empty input");
}
}
/**
* Converts the first character of the input into lower case.
* Example: If the input is GetWord, the return value will be getWord
* @param input
* @return
*/
public String applyMethodNamingPolicy(String input) {
if(input != null && input.length() > 0) {
return convertToUnderscoreName(input);
}else{
throw new CodeGenerationException("Error converting input to first letter to lower because of null or empty input");
}
}
/**
* Generate name of the service from resource path.
*
* Example: if input is /user.json the generated name for this path will be UserAPI
* If the input is /user.json/{userId}, the service name will still be generated as UserAPI
*
* @param resourcePath
* @return
*/
public String getServiceName(String resourcePath) {
String className = null;
int index = resourcePath.indexOf(".");
if(index >= 0) {
String resourceName = resourcePath.substring(1,index);
className = applyClassNamingPolicy(resourceName);
}else{
String[] paths = resourcePath.split("/");
for(String path : paths) {
if(path != null && path.length() > 0) {
className = applyClassNamingPolicy(path);
break;
}
}
}
return className + "_api";
}
/**
* Generates the name of service methods.
*
* Resource documentation provides suggested names. Individual language can choose to use suggested name or
* generate the name based on end point path. Example: IN java we use suggested name
*
* @param endPoint
* @param suggestedName
* @return
*/
public String getMethodName(String endPoint, String suggestedName) {
return convertToUnderscoreName(suggestedName);
}
/**
* For input UserAPI and resource path /findUserById the suggested input object name will be: UserFindUserByIdInput
*
* If the input path is /{userId}/delete the suggested name will be UserDeleteInput. The path parameters are ignored
* in generating the input object name
*
* Note: Input objects are only created when the number of input arguments in a method exceeds certain number so <br/> that the method signatures are clean
*
*
* @param serviceName
* @param resourcePath
* @return
*/
public String getInputObjectName(String serviceName, String resourcePath) {
//Since service name has API at the end remove that format he name
String inputobjectName = serviceName.substring(0, serviceName.length() - 3);
String[] pathElements = resourcePath.split("/");
StringBuilder urlPath = new StringBuilder("");
if(pathElements != null){
for(int i=0; i < pathElements.length; i++){
String pathElement = pathElements[i];
if(pathElement != null && pathElement.length() > 0) {
int position = pathElement.indexOf("{");
if(position < 0) {
inputobjectName = inputobjectName + applyClassNamingPolicy(pathElement) + INPUT_OBJECT_SUFFIX;
}
}
}
}
return inputobjectName;
}
/**
* Generate the name of the wrapper class used as a wrapper for a list of items returned by a service
* <p/>
* Example: get definitions API returns a list of definition objects as the result. This will be wrapped by an
* object. The object's name will be determined by invoking this service.
* eg. DefinitionList for a wrapper of 'definition's
*
* @param wrapperItemName
* @return
*/
public String getListWrapperName(String wrapperItemName) {
return applyClassNamingPolicy(wrapperItemName) + "_list";
}
/**
* Generates a name for an enum for the param or field name.
* <p/>
* Example: for a param source the return could be SourceEnum
*
* @param input
* @return
*/
public String getEnumName(String input) {
if (input != null && input.length() > 0) {
return this.applyClassNamingPolicy(input).concat("_values");
} else {
throw new CodeGenerationException("Error getting Enum name becuase of null input");
}
}
/**
* Gets the signature of the method that gets value for give attribute name.
*
* Example: If class name is user and attibute name is email the out in java language will be
* <code>user.getEmail()</code>
*
* @param className
* @param attributeName
* @return
*/
public String createGetterMethodName(String className, String attributeName) {
return className+".get_"+ applyClassNamingPolicy(attributeName)+"()";
}
}

View File

@@ -59,12 +59,12 @@ class ScalaLibCodeGen(
libraryHome: String,
configPath: String) extends LibraryCodeGenerator {
this.reservedWordMapper = new ScalaReservedWordMapper
if (null != configPath) {
initializeWithConfigPath(configPath)
this.setDataTypeMappingProvider(new ScalaDataTypeMappingProvider())
this.setNameGenerator(new CamelCaseNamingPolicyProvider())
}
else{
} else {
initialize(apiServerURL, apiKey, modelPackageName, apiPackageName, classOutputDir, libraryHome)
setDataTypeMappingProvider(new ScalaDataTypeMappingProvider())
setNameGenerator(new CamelCaseNamingPolicyProvider())

View File

@@ -0,0 +1,32 @@
/**
* Copyright 2011 Wordnik, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wordnik.swagger.codegen.config.scala
import com.wordnik.swagger.codegen.config.ReservedWordMapper
object ScalaKeywordMapper {
val reservedWords = Array("type", "case")
}
class ScalaReservedWordMapper extends ReservedWordMapper {
override def translate(input: String): String = {
ScalaKeywordMapper.reservedWords.contains(input) match {
case true => "`" + input + "`"
case false => input
}
}
}

View File

@@ -24,11 +24,8 @@ import java.util.List;
* Time: 8:31 AM
*/
public class Model {
private String name;
private String description;
private List<ModelField> fields;
public String getName() {

View File

@@ -31,29 +31,17 @@ import java.util.StringTokenizer;
* Time: 7:57 AM
*/
public class ModelField {
private String name;
private String wrapperName;
private String description = "";
private String defaultValue;
private boolean required = false;
private boolean allowMultiple = false;
private List<String> allowableValues = null;
private String paramType;
private String dataType;
private String internalDescription;
private String paramAccess;
private FieldDefinition fieldDefinition;
public String getName() {

View File

@@ -32,10 +32,8 @@ import java.util.Map;
* Time: 7:01 PM
*/
public class Resource {
private String apiVersion;
//TODO rename the JSON property too after the sandbox var has been renamed
@JsonProperty("swaggerVersion")
private String swaggerVersion;
@@ -48,11 +46,8 @@ public class Resource {
@JsonProperty("models")
private ApiModelListWrapper modelListWrapper;
private List<Model> models = new ArrayList<Model>();
private String generatedClassName;
private List<ResourceMethod> methods;
@JsonCreator
@@ -117,13 +112,13 @@ public class Resource {
}*/
public String generateClassName(NamingPolicyProvider nameGenerator) {
if (generatedClassName == null) {
if (generatedClassName == null && endPoints.size() > 0) {
String endPointPath = endPoints.get(0).getPath();
generatedClassName = nameGenerator.getServiceName(endPointPath);
}
}
return generatedClassName;
}
public List<ResourceMethod> generateMethods(Resource resource, DataTypeMappingProvider dataTypeMapper, NamingPolicyProvider nameGenerator) {
if(methods == null){
methods = new ArrayList<ResourceMethod>();
@@ -146,7 +141,5 @@ public class Resource {
models.add (modelDefn.toModel(modelName, nameGenerator) );
}
}
}
}