com.googlecode.jsonschema2pojo.rules.ObjectRule Maven / Gradle / Ivy
/**
* Copyright © 2010-2011 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 com.googlecode.jsonschema2pojo.rules;
import static org.apache.commons.lang.StringUtils.*;
import java.io.Serializable;
import java.lang.reflect.Modifier;
import javax.annotation.Generated;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import com.googlecode.jsonschema2pojo.Schema;
import com.googlecode.jsonschema2pojo.SchemaMapper;
import com.googlecode.jsonschema2pojo.exception.ClassAlreadyExistsException;
import com.sun.codemodel.JAnnotationUse;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JClassContainer;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JInvocation;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JPackage;
import com.sun.codemodel.JType;
import com.sun.codemodel.JVar;
/**
* Applies the generation steps required for schemas of type "object".
*
* @see http://tools.ietf.org/html/draft-zyp-json-schema-02#section-5.1
*/
public class ObjectRule implements SchemaRule {
private static final String ILLEGAL_CHARACTER_REGEX = "[^0-9a-zA-Z]";
private final RuleFactory ruleFactory;
protected ObjectRule(RuleFactory ruleFactory) {
this.ruleFactory = ruleFactory;
}
/**
* Applies this schema rule to take the required code generation steps.
*
* When this rule is applied for schemas of type object, the properties of
* the schema are used to generate a new Java class and determine its
* characteristics. See other implementers of {@link SchemaRule} for
* details.
*
* A new Java type will be created when this rule is applied, it is
* annotated as {@link Generated}, it is given equals
,
* hashCode
and toString
methods and implements
* {@link Serializable}.
*/
@Override
public JType apply(String nodeName, JsonNode node, JPackage _package, Schema schema) {
JType superType = getSuperType(nodeName, node, _package, schema);
if (superType.isPrimitive() || isFinal(superType)) {
return superType;
}
JDefinedClass jclass;
try {
jclass = createClass(nodeName, node, _package);
} catch (ClassAlreadyExistsException e) {
return e.getExistingClass();
}
jclass._extends((JClass) superType);
schema.setJavaTypeIfEmpty(jclass);
addGeneratedAnnotation(jclass);
addSerializable(jclass);
if (node.has("title")) {
ruleFactory.getTitleRule().apply(nodeName, node.get("title"), jclass, schema);
}
if (node.has("description")) {
ruleFactory.getDescriptionRule().apply(nodeName, node.get("description"), jclass, schema);
}
if (node.has("properties")) {
ruleFactory.getPropertiesRule().apply(nodeName, node.get("properties"), jclass, schema);
}
addToString(jclass);
addHashCode(jclass);
addEquals(jclass);
ruleFactory.getAdditionalPropertiesRule().apply(nodeName, node.get("additionalProperties"), jclass, schema);
return jclass;
}
/**
* Creates a new Java class that will be generated.
*
* @param nodeName
* the node name which may be used to dictate the new class name
* @param node
* the node representing the schema that caused the need for a
* new class. This node may include a 'javaType' property which
* if present will override the fully qualified name of the newly
* generated class.
* @param _package
* the package which may contain a new class after this method
* call
* @return a reference to a newly created class
* @throws ClassAlreadyExistsException
* if the given arguments cause an attempt to create a class
* that already exists, either on the classpath or in the
* current map of classes to be generated.
*/
private JDefinedClass createClass(String nodeName, JsonNode node, JPackage _package) throws ClassAlreadyExistsException {
JDefinedClass newType;
try {
if (node.has("javaType")) {
String fqn = node.get("javaType").getTextValue();
try {
Class> existingClass = Thread.currentThread().getContextClassLoader().loadClass(fqn);
throw new ClassAlreadyExistsException(_package.owner().ref(existingClass));
} catch (ClassNotFoundException e) {
newType = _package.owner()._class(fqn);
}
} else {
newType = _package._class(getClassName(nodeName));
}
} catch (JClassAlreadyExistsException e) {
throw new ClassAlreadyExistsException(e.getExistingClass());
}
return addAnnotations(newType);
}
/**
* Add any class-level annotations required for a newly generated class.
*
* @param _class
* the newly generated class
* @return
*/
private JDefinedClass addAnnotations(JDefinedClass _class) {
_class.annotate(JsonSerialize.class).param("include", JsonSerialize.Inclusion.NON_NULL);
return _class;
}
private boolean isFinal(JType superType) {
try {
Class> javaClass = Class.forName(superType.fullName());
return Modifier.isFinal(javaClass.getModifiers());
} catch (ClassNotFoundException e) {
return false;
}
}
private JType getSuperType(String nodeName, JsonNode node, JClassContainer jClassContainer, Schema schema) {
JType superType = jClassContainer.owner().ref(Object.class);
if (node.has("extends")) {
superType = ruleFactory.getSchemaRule().apply(nodeName + "Parent", node.get("extends"), jClassContainer, schema);
}
return superType;
}
private void addSerializable(JDefinedClass jclass) {
jclass._implements(Serializable.class);
}
private void addGeneratedAnnotation(JDefinedClass jclass) {
JAnnotationUse generated = jclass.annotate(Generated.class);
generated.param("value", SchemaMapper.class.getPackage().getName());
}
private void addToString(JDefinedClass jclass) {
JMethod toString = jclass.method(JMod.PUBLIC, String.class, "toString");
JBlock body = toString.body();
JInvocation reflectionToString = jclass.owner().ref(ToStringBuilder.class).staticInvoke("reflectionToString");
reflectionToString.arg(JExpr._this());
body._return(reflectionToString);
toString.annotate(Override.class);
}
private void addHashCode(JDefinedClass jclass) {
JMethod hashCode = jclass.method(JMod.PUBLIC, int.class, "hashCode");
JBlock body = hashCode.body();
JInvocation reflectionHashCode = jclass.owner().ref(HashCodeBuilder.class).staticInvoke("reflectionHashCode");
reflectionHashCode.arg(JExpr._this());
body._return(reflectionHashCode);
hashCode.annotate(Override.class);
}
private void addEquals(JDefinedClass jclass) {
JMethod equals = jclass.method(JMod.PUBLIC, boolean.class, "equals");
JVar otherObject = equals.param(Object.class, "other");
JBlock body = equals.body();
JInvocation reflectionEquals = jclass.owner().ref(EqualsBuilder.class).staticInvoke("reflectionEquals");
reflectionEquals.arg(JExpr._this());
reflectionEquals.arg(otherObject);
body._return(reflectionEquals);
equals.annotate(Override.class);
}
private String getClassName(String nodeName) {
return capitalize(nodeName).replaceAll(ILLEGAL_CHARACTER_REGEX, "_");
}
}