org.jsonschema2pojo.rules.DynamicPropertiesRule Maven / Gradle / Ivy
/**
* Copyright © 2010-2020 Nokia
*
* 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 org.jsonschema2pojo.rules;
import static com.sun.codemodel.JExpr.*;
import static com.sun.codemodel.JMod.*;
import java.util.Iterator;
import java.util.Map;
import org.jsonschema2pojo.Schema;
import org.jsonschema2pojo.util.LanguageFeatures;
import org.jsonschema2pojo.util.Models;
import com.fasterxml.jackson.databind.JsonNode;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JConditional;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpression;
import com.sun.codemodel.JFieldRef;
import com.sun.codemodel.JInvocation;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JOp;
import com.sun.codemodel.JSwitch;
import com.sun.codemodel.JType;
import com.sun.codemodel.JTypeVar;
import com.sun.codemodel.JVar;
/**
* Adds methods for dynamically getting, setting, and building properties.
*
* @author Christian Trimble
*
*/
public class DynamicPropertiesRule implements Rule {
public static final String NOT_FOUND_VALUE_FIELD = "NOT_FOUND_VALUE";
public static final String SETTER_NAME = "set";
public static final String GETTER_NAME = "get";
public static final String BUILDER_NAME = "with";
public static final String DEFINED_SETTER_NAME = "declaredProperty";
public static final String DEFINED_GETTER_NAME = "declaredPropertyOrNotFound";
private RuleFactory ruleFactory;
public DynamicPropertiesRule(RuleFactory ruleFactory) {
this.ruleFactory = ruleFactory;
}
/**
* This rule adds dynamic getter, setter and builder methods based on the properties and additional properties
* defined in a schema.
*
* If accessors are being generated, then methods for getting and setting properties by name will be added. These
* methods first attempt to call the appropriate getter or setter for the property. If the named property is not defined,
* then the additional properties map is used.
*
* If builders are being generated, then a method for building properties by name will be added. This method first
* attempts to call the builder for the property. If no property with the supplied name is defined, then the additional
* properties map is used.
*
* The methods generated by this class throw an IllegalArgumentException, if the name specified for the property is unknown and
* additional properties are not enabled. A ClassCastException will be thrown, when the value being set is incompatible with the
* type of the named property.
*
* @param nodeName
* the name of the node for which dynamic getters, setters, and builders are being added.
* @param node
* the properties node, containing property names and their
* definition
* @param parent
* the parent node
* @param jclass
* the Java type which will have the given properties added
* @param currentSchema
* the schema being implemented
* @return the given jclass
*/
@Override
public JDefinedClass apply(String nodeName, JsonNode node, JsonNode parent, JDefinedClass jclass, Schema currentSchema) {
if (!ruleFactory.getGenerationConfig().isIncludeDynamicAccessors() ||
(!ruleFactory.getGenerationConfig().isIncludeDynamicSetters() &&
!ruleFactory.getGenerationConfig().isIncludeDynamicGetters() &&
!ruleFactory.getGenerationConfig().isIncludeDynamicBuilders())) {
return jclass;
}
boolean isIncludeGetters = ruleFactory.getGenerationConfig().isIncludeGetters();
boolean isIncludeSetters = ruleFactory.getGenerationConfig().isIncludeSetters();
boolean isGenerateBuilders = ruleFactory.getGenerationConfig().isGenerateBuilders();
if (isIncludeGetters || isIncludeSetters || isGenerateBuilders) {
if (LanguageFeatures.canUseJava7(ruleFactory.getGenerationConfig())) {
if (isIncludeSetters) {
addInternalSetMethodJava7(jclass, node);
}
if (isIncludeGetters) {
addInternalGetMethodJava7(jclass, node);
}
} else {
if (isIncludeSetters) {
addInternalSetMethodJava6(jclass, node);
}
if (isIncludeGetters) {
addInternalGetMethodJava6(jclass, node);
}
}
}
if (isIncludeGetters) {
addGetMethods(jclass);
}
if (isIncludeSetters) {
addSetMethods(jclass);
}
if (isGenerateBuilders) {
addWithMethods(jclass);
}
return jclass;
}
void addGetMethods(JDefinedClass jclass) {
JFieldRef notFoundVar = getOrAddNotFoundVar(jclass);
JMethod internalGetMethod = this.getInternalGetMethod(jclass);
addPublicGetMethod(jclass, internalGetMethod, notFoundVar);
}
JFieldRef getOrAddNotFoundVar(JDefinedClass jclass) {
jclass.field(PROTECTED | STATIC | FINAL, Object.class, NOT_FOUND_VALUE_FIELD,
_new(jclass.owner()._ref(Object.class)));
return jclass.staticRef(NOT_FOUND_VALUE_FIELD);
}
private JMethod addPublicGetMethod(JDefinedClass jclass, JMethod internalGetMethod, JFieldRef notFoundValue) {
JMethod method = jclass.method(PUBLIC, jclass.owner()._ref(Object.class), GETTER_NAME);
JTypeVar returnType = method.generify("T");
method.type(returnType);
Models.suppressWarnings(method, "unchecked");
JVar nameParam = method.param(String.class, "name");
JBlock body = method.body();
JVar valueVar = body.decl(jclass.owner()._ref(Object.class), "value",
invoke(internalGetMethod).arg(nameParam).arg(notFoundValue));
JConditional found = method.body()._if(notFoundValue.ne(valueVar));
found._then()._return(cast(returnType, valueVar));
JBlock notFound = found._else();
JMethod getAdditionalProperties = jclass.getMethod("getAdditionalProperties", new JType[] {});
if (getAdditionalProperties != null) {
notFound._return(cast(returnType, invoke(getAdditionalProperties).invoke("get").arg(nameParam)));
} else {
notFound._throw(illegalArgumentInvocation(jclass, nameParam));
}
return method;
}
private JMethod addInternalGetMethodJava7(JDefinedClass jclass, JsonNode propertiesNode) {
JMethod method = jclass.method(PROTECTED, jclass.owner()._ref(Object.class), DEFINED_GETTER_NAME);
JVar nameParam = method.param(String.class, "name");
JVar notFoundParam = method.param(jclass.owner()._ref(Object.class), "notFoundValue");
JBlock body = method.body();
JSwitch propertySwitch = body._switch(nameParam);
if (propertiesNode != null) {
for (Iterator> properties = propertiesNode.fields(); properties.hasNext();) {
Map.Entry property = properties.next();
String propertyName = property.getKey();
JsonNode node = property.getValue();
String fieldName = ruleFactory.getNameHelper().getPropertyName(propertyName, node);
JType propertyType = jclass.fields().get(fieldName).type();
addGetPropertyCase(jclass, propertySwitch, propertyName, propertyType, node);
}
}
JClass extendsType = jclass._extends();
if (extendsType != null && extendsType instanceof JDefinedClass) {
JDefinedClass parentClass = (JDefinedClass) extendsType;
JMethod parentMethod = parentClass.getMethod(DEFINED_GETTER_NAME,
new JType[] { parentClass.owner()._ref(String.class), parentClass.owner()._ref(Object.class) });
propertySwitch._default().body()
._return(_super().invoke(parentMethod).arg(nameParam).arg(notFoundParam));
} else {
propertySwitch._default().body()
._return(notFoundParam);
}
return method;
}
private JMethod addInternalGetMethodJava6(JDefinedClass jclass, JsonNode propertiesNode) {
JMethod method = jclass.method(PROTECTED, jclass.owner()._ref(Object.class), DEFINED_GETTER_NAME);
JVar nameParam = method.param(String.class, "name");
JVar notFoundParam = method.param(jclass.owner()._ref(Object.class), "notFoundValue");
JBlock body = method.body();
JConditional propertyConditional = null;
if (propertiesNode != null) {
for (Iterator> properties = propertiesNode.fields(); properties.hasNext();) {
Map.Entry property = properties.next();
String propertyName = property.getKey();
JsonNode node = property.getValue();
String fieldName = ruleFactory.getNameHelper().getPropertyName(propertyName, node);
JType propertyType = jclass.fields().get(fieldName).type();
JExpression condition = lit(propertyName).invoke("equals").arg(nameParam);
if (propertyConditional == null) {
propertyConditional = body._if(condition);
} else {
propertyConditional = propertyConditional._elseif(condition);
}
JMethod propertyGetter = jclass.getMethod(getGetterName(propertyName, propertyType, node), new JType[] {});
propertyConditional._then()._return(invoke(propertyGetter));
}
}
JClass extendsType = jclass._extends();
JBlock lastBlock = propertyConditional == null ? body : propertyConditional._else();
if (extendsType != null && extendsType instanceof JDefinedClass) {
JDefinedClass parentClass = (JDefinedClass) extendsType;
JMethod parentMethod = parentClass.getMethod(DEFINED_GETTER_NAME,
new JType[] { parentClass.owner()._ref(String.class), parentClass.owner()._ref(Object.class) });
lastBlock._return(_super().invoke(parentMethod).arg(nameParam).arg(notFoundParam));
} else {
lastBlock._return(notFoundParam);
}
return method;
}
private void addGetPropertyCase(JDefinedClass jclass, JSwitch propertySwitch, String propertyName, JType propertyType, JsonNode node) {
JMethod propertyGetter = jclass.getMethod(getGetterName(propertyName, propertyType, node), new JType[] {});
propertySwitch._case(lit(propertyName)).body()
._return(invoke(propertyGetter));
}
private void addSetMethods(JDefinedClass jclass) {
JMethod internalSetMethod = getInternalSetMethod(jclass);
addPublicSetMethod(jclass, internalSetMethod);
}
private JMethod addPublicSetMethod(JDefinedClass jclass, JMethod internalSetMethod) {
JMethod method = jclass.method(PUBLIC, jclass.owner().VOID, SETTER_NAME);
JVar nameParam = method.param(String.class, "name");
JVar valueParam = method.param(Object.class, "value");
JBlock body = method.body();
JBlock notFound = body._if(JOp.not(invoke(internalSetMethod).arg(nameParam).arg(valueParam)))._then();
// if we have additional properties, then put value.
JMethod getAdditionalProperties = jclass.getMethod("getAdditionalProperties", new JType[] {});
if (getAdditionalProperties != null) {
JType additionalPropertiesType = ((JClass) (getAdditionalProperties.type())).getTypeParameters().get(1);
notFound.add(invoke(getAdditionalProperties).invoke("put").arg(nameParam)
.arg(cast(additionalPropertiesType, valueParam)));
}
// else throw exception.
else {
notFound._throw(illegalArgumentInvocation(jclass, nameParam));
}
return method;
}
private void addWithMethods(JDefinedClass jclass) {
JMethod internalSetMethod = getInternalSetMethod(jclass);
addPublicWithMethod(jclass, internalSetMethod);
}
private JMethod addPublicWithMethod(JDefinedClass jclass, JMethod internalSetMethod) {
JMethod method = jclass.method(PUBLIC, jclass, BUILDER_NAME);
JVar nameParam = method.param(String.class, "name");
JVar valueParam = method.param(Object.class, "value");
JBlock body = method.body();
JBlock notFound = body._if(JOp.not(invoke(internalSetMethod).arg(nameParam).arg(valueParam)))._then();
// if we have additional properties, then put value.
JMethod getAdditionalProperties = jclass.getMethod("getAdditionalProperties", new JType[] {});
if (getAdditionalProperties != null) {
JType additionalPropertiesType = ((JClass) (getAdditionalProperties.type())).getTypeParameters().get(1);
notFound.add(invoke(getAdditionalProperties).invoke("put").arg(nameParam)
.arg(cast(additionalPropertiesType, valueParam)));
}
// else throw exception.
else {
notFound._throw(illegalArgumentInvocation(jclass, nameParam));
}
body._return(_this());
return method;
}
private JMethod addInternalSetMethodJava7(JDefinedClass jclass, JsonNode propertiesNode) {
JMethod method = jclass.method(PROTECTED, jclass.owner().BOOLEAN, DEFINED_SETTER_NAME);
JVar nameParam = method.param(String.class, "name");
JVar valueParam = method.param(Object.class, "value");
JBlock body = method.body();
JSwitch propertySwitch = body._switch(nameParam);
if (propertiesNode != null) {
for (Iterator> properties = propertiesNode.fields(); properties.hasNext();) {
Map.Entry property = properties.next();
String propertyName = property.getKey();
JsonNode node = property.getValue();
String fieldName = ruleFactory.getNameHelper().getPropertyName(propertyName, node);
JType propertyType = jclass.fields().get(fieldName).type();
addSetPropertyCase(jclass, propertySwitch, propertyName, propertyType, valueParam, node);
}
}
JBlock defaultBlock = propertySwitch._default().body();
JClass extendsType = jclass._extends();
if (extendsType != null && extendsType instanceof JDefinedClass) {
JDefinedClass parentClass = (JDefinedClass) extendsType;
JMethod parentMethod = parentClass.getMethod(DEFINED_SETTER_NAME,
new JType[] { parentClass.owner()._ref(String.class), parentClass.owner()._ref(Object.class) });
defaultBlock._return(_super().invoke(parentMethod).arg(nameParam).arg(valueParam));
} else {
defaultBlock._return(FALSE);
}
return method;
}
private JMethod addInternalSetMethodJava6(JDefinedClass jclass, JsonNode propertiesNode) {
JMethod method = jclass.method(PROTECTED, jclass.owner().BOOLEAN, DEFINED_SETTER_NAME);
JVar nameParam = method.param(String.class, "name");
JVar valueParam = method.param(Object.class, "value");
JBlock body = method.body();
JConditional propertyConditional = null;
if (propertiesNode != null) {
for (Iterator> properties = propertiesNode.fields(); properties.hasNext();) {
Map.Entry property = properties.next();
String propertyName = property.getKey();
JsonNode node = property.getValue();
String fieldName = ruleFactory.getNameHelper().getPropertyName(propertyName, node);
JType propertyType = jclass.fields().get(fieldName).type();
JExpression condition = lit(propertyName).invoke("equals").arg(nameParam);
propertyConditional = propertyConditional == null ? propertyConditional = body._if(condition)
: propertyConditional._elseif(condition);
JBlock callSite = propertyConditional._then();
addSetProperty(jclass, callSite, propertyName, propertyType, valueParam, node);
callSite._return(TRUE);
}
}
JClass extendsType = jclass._extends();
JBlock lastBlock = propertyConditional == null ? body : propertyConditional._else();
if (extendsType != null && extendsType instanceof JDefinedClass) {
JDefinedClass parentClass = (JDefinedClass) extendsType;
JMethod parentMethod = parentClass.getMethod(DEFINED_SETTER_NAME,
new JType[] { parentClass.owner()._ref(String.class), parentClass.owner()._ref(Object.class) });
lastBlock._return(_super().invoke(parentMethod).arg(nameParam).arg(valueParam));
} else {
lastBlock._return(FALSE);
}
return method;
}
private JMethod getInternalSetMethod(JDefinedClass jclass) {
return jclass.getMethod(DEFINED_SETTER_NAME,
new JType[] { jclass.owner().ref(String.class), jclass.owner().ref(Object.class) });
}
private JMethod getInternalGetMethod(JDefinedClass jclass) {
return jclass.getMethod(DEFINED_GETTER_NAME,
new JType[] { jclass.owner().ref(String.class), jclass.owner().ref(Object.class) });
}
private void addSetPropertyCase(JDefinedClass jclass, JSwitch setterSwitch, String propertyName, JType propertyType, JVar valueVar, JsonNode node) {
JBlock setterBody = setterSwitch._case(lit(propertyName)).body();
addSetProperty(jclass, setterBody, propertyName, propertyType, valueVar, node);
setterBody._return(TRUE);
}
private void addSetProperty(JDefinedClass jclass, JBlock callSite, String propertyName, JType propertyType, JVar valueVar, JsonNode node) {
JMethod propertySetter = jclass.getMethod(getSetterName(propertyName, node), new JType[] { propertyType });
JConditional isInstance = callSite._if(valueVar._instanceof(propertyType.boxify().erasure()));
isInstance._then()
.invoke(propertySetter).arg(cast(propertyType.boxify(), valueVar));
isInstance._else()
._throw(illegalArgumentInvocation(jclass, propertyName, propertyType, valueVar));
}
private JInvocation illegalArgumentInvocation(JDefinedClass jclass, JVar propertyName) {
return _new(jclass.owner()._ref(IllegalArgumentException.class))
.arg(lit("property \"").plus(propertyName).plus(lit("\" is not defined")));
}
private JInvocation illegalArgumentInvocation(JDefinedClass jclass, String propertyName, JType propertyType, JVar valueVar) {
return _new(jclass.owner()._ref(IllegalArgumentException.class))
.arg(lit("property \"" + propertyName + "\" is of type \"" + propertyType.fullName() + "\", but got ")
.plus(valueVar.invoke("getClass").invoke("toString")));
}
private String getSetterName(String propertyName, JsonNode node) {
return ruleFactory.getNameHelper().getSetterName(propertyName, node);
}
private String getGetterName(String propertyName, JType type, JsonNode node) {
return ruleFactory.getNameHelper().getGetterName(propertyName, type, node);
}
}