All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tigrisdata.tools.schema.TigrisDBObjectRule Maven / Gradle / Ivy

There is a newer version: 1.0.0-alpha.3
Show newest version
/*
 * Copyright © 2010-2020 Nokia
 * Copyright 2022 Tigris Data, 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.tigrisdata.tools.schema;

import com.fasterxml.jackson.databind.JsonNode;
import com.sun.codemodel.ClassType;
import com.sun.codemodel.JAnnotationUse;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JConditional;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JExpression;
import com.sun.codemodel.JFieldRef;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JOp;
import com.sun.codemodel.JPackage;
import com.sun.codemodel.JType;
import com.sun.codemodel.JVar;
import static org.apache.commons.lang3.StringUtils.substringAfter;
import static org.apache.commons.lang3.StringUtils.substringBefore;
import org.jsonschema2pojo.Annotator;
import org.jsonschema2pojo.Schema;
import org.jsonschema2pojo.exception.ClassAlreadyExistsException;
import org.jsonschema2pojo.exception.GenerationException;
import static org.jsonschema2pojo.rules.PrimitiveTypes.isPrimitive;
import static org.jsonschema2pojo.rules.PrimitiveTypes.primitiveType;
import org.jsonschema2pojo.rules.Rule;
import org.jsonschema2pojo.rules.RuleFactory;
import org.jsonschema2pojo.util.ParcelableHelper;
import org.jsonschema2pojo.util.ReflectionHelper;
import org.jsonschema2pojo.util.SerializableHelper;
import static org.jsonschema2pojo.util.TypeUtil.resolveType;

import javax.annotation.Generated;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * Applies the generation steps required for schemas of type "object". This class was leveraged from
 * 3rd party library and modified. In its original form it lacks the ability to customize.
 *
 * @see http:/
 *     /tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1
 */
public class TigrisDBObjectRule implements Rule {

  private final RuleFactory ruleFactory;
  private final ReflectionHelper reflectionHelper;
  private final ParcelableHelper parcelableHelper;

  protected TigrisDBObjectRule(
      RuleFactory ruleFactory,
      ParcelableHelper parcelableHelper,
      ReflectionHelper reflectionHelper) {
    this.ruleFactory = ruleFactory;
    this.parcelableHelper = parcelableHelper;
    this.reflectionHelper = reflectionHelper;
  }

  /**
   * 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 Rule} for details. */ @Override public JType apply( String nodeName, JsonNode node, JsonNode parent, JPackage _package, Schema schema) { JType superType = reflectionHelper.getSuperType(nodeName, node, _package, schema); if (superType.isPrimitive() || reflectionHelper.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); if (node.has("title")) { ruleFactory.getTitleRule().apply(nodeName, node.get("title"), node, jclass, schema); } if (node.has("description")) { ruleFactory .getDescriptionRule() .apply(nodeName, node.get("description"), node, jclass, schema); } if (node.has("$comment")) { ruleFactory.getCommentRule().apply(nodeName, node.get("$comment"), node, jclass, schema); } // Creates the class definition for the builder if (ruleFactory.getGenerationConfig().isGenerateBuilders() && ruleFactory.getGenerationConfig().isUseInnerClassBuilders()) { ruleFactory.getBuilderRule().apply(nodeName, node, parent, jclass, schema); } ruleFactory.getPropertiesRule().apply(nodeName, node.get("properties"), node, jclass, schema); if (node.has("javaInterfaces")) { addInterfaces(jclass, node.get("javaInterfaces")); } ruleFactory .getAdditionalPropertiesRule() .apply(nodeName, node.get("additionalProperties"), node, jclass, schema); ruleFactory .getDynamicPropertiesRule() .apply(nodeName, node.get("properties"), node, jclass, schema); if (node.has("required")) { ruleFactory .getRequiredArrayRule() .apply(nodeName, node.get("required"), node, jclass, schema); } if (ruleFactory.getGenerationConfig().isIncludeGeneratedAnnotation()) { addGeneratedAnnotation(jclass); } if (ruleFactory.getGenerationConfig().isIncludeToString()) { addToString(jclass); } if (ruleFactory.getGenerationConfig().isIncludeHashcodeAndEquals()) { addHashCode(jclass, node); addEquals(jclass, node); } if (ruleFactory.getGenerationConfig().isParcelable()) { addParcelSupport(jclass); } if (ruleFactory.getGenerationConfig().isIncludeConstructors()) { ruleFactory.getConstructorRule().apply(nodeName, node, parent, jclass, schema); } if (ruleFactory.getGenerationConfig().isSerializable()) { SerializableHelper.addSerializableSupport(jclass); } // implement custom marker interface jclass._implements(com.tigrisdata.db.client.model.TigrisCollectionType.class); return jclass; } private void addParcelSupport(JDefinedClass jclass) { jclass._implements(jclass.owner().directClass("android.os.Parcelable")); parcelableHelper.addWriteToParcel(jclass); parcelableHelper.addDescribeContents(jclass); parcelableHelper.addCreator(jclass); parcelableHelper.addConstructorFromParcel(jclass); // #742 : includeConstructors will include the default constructor if (!ruleFactory.getGenerationConfig().isIncludeConstructors()) { // Add empty constructor jclass.constructor(JMod.PUBLIC); } } /** * 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; Annotator annotator = ruleFactory.getAnnotator(); try { if (node.has("existingJavaType")) { String fqn = substringBefore(node.get("existingJavaType").asText(), "<"); if (isPrimitive(fqn, _package.owner())) { throw new ClassAlreadyExistsException(primitiveType(fqn, _package.owner())); } JClass existingClass = resolveType( _package, fqn + (node.get("existingJavaType").asText().contains("<") ? "<" + substringAfter(node.get("existingJavaType").asText(), "<") : "")); throw new ClassAlreadyExistsException(existingClass); } boolean usePolymorphicDeserialization = annotator.isPolymorphicDeserializationSupported(node); if (node.has("javaType")) { String fqn = node.path("javaType").asText(); if (isPrimitive(fqn, _package.owner())) { throw new GenerationException( "javaType cannot refer to a primitive type (" + fqn + "), did you mean to use existingJavaType?"); } if (fqn.contains("<")) { throw new GenerationException( "javaType does not support generic args (" + fqn + "), did you mean to use existingJavaType?"); } int index = fqn.lastIndexOf(".") + 1; if (index == 0) { // Actually not a fully qualified name fqn = _package.name() + "." + fqn; index = fqn.lastIndexOf(".") + 1; } if (index >= 0 && index < fqn.length()) { fqn = fqn.substring(0, index) + ruleFactory.getGenerationConfig().getClassNamePrefix() + fqn.substring(index) + ruleFactory.getGenerationConfig().getClassNameSuffix(); } if (usePolymorphicDeserialization) { newType = _package.owner()._class(JMod.PUBLIC, fqn, ClassType.CLASS); } else { newType = _package.owner()._class(fqn); } } else { if (usePolymorphicDeserialization) { newType = _package._class( JMod.PUBLIC, ruleFactory.getNameHelper().getUniqueClassName(nodeName, node, _package), ClassType.CLASS); } else { newType = _package._class( ruleFactory.getNameHelper().getUniqueClassName(nodeName, node, _package)); } } } catch (JClassAlreadyExistsException e) { throw new ClassAlreadyExistsException(e.getExistingClass()); } annotator.typeInfo(newType, node); annotator.propertyInclusion(newType, node); return newType; } private void addToString(JDefinedClass jclass) { Map fields = jclass.fields(); JMethod toString = jclass.method(JMod.PUBLIC, String.class, "toString"); Set excludes = new HashSet<>(Arrays.asList(ruleFactory.getGenerationConfig().getToStringExcludes())); JBlock body = toString.body(); // The following toString implementation roughly matches the commons ToStringBuilder for // backward compatibility JClass stringBuilderClass = jclass.owner().ref(StringBuilder.class); JVar sb = body.decl(stringBuilderClass, "sb", JExpr._new(stringBuilderClass)); // Write the header, e.g.: example.domain.MyClass@85e382a7[ body.add( sb.invoke("append") .arg(jclass.dotclass().invoke("getName")) .invoke("append") .arg(JExpr.lit('@')) .invoke("append") .arg( jclass .owner() .ref(Integer.class) .staticInvoke("toHexString") .arg( jclass .owner() .ref(System.class) .staticInvoke("identityHashCode") .arg(JExpr._this()))) .invoke("append") .arg(JExpr.lit('['))); // If this has a parent class, include its toString() if (!jclass._extends().fullName().equals(Object.class.getName())) { JVar baseLength = body.decl(jclass.owner().INT, "baseLength", sb.invoke("length")); JVar superString = body.decl( jclass.owner().ref(String.class), "superString", JExpr._super().invoke("toString")); JBlock superToStringBlock = body._if(superString.ne(JExpr._null()))._then(); // If super.toString() is in the Clazz@2ee6529d[field=10] format, extract the fields // from the wrapper JVar contentStart = superToStringBlock.decl( jclass.owner().INT, "contentStart", superString.invoke("indexOf").arg(JExpr.lit('['))); JVar contentEnd = superToStringBlock.decl( jclass.owner().INT, "contentEnd", superString.invoke("lastIndexOf").arg(JExpr.lit(']'))); JConditional superToStringInnerConditional = superToStringBlock._if(contentStart.gte(JExpr.lit(0)).cand(contentEnd.gt(contentStart))); superToStringInnerConditional ._then() .add( sb.invoke("append") .arg(superString) .arg(contentStart.plus(JExpr.lit(1))) .arg(contentEnd)); // Otherwise, just append super.toString() superToStringInnerConditional._else().add(sb.invoke("append").arg(superString)); // Append a comma if needed body._if(sb.invoke("length").gt(baseLength)) ._then() .add(sb.invoke("append").arg(JExpr.lit(','))); } // For each included instance field, add to the StringBuilder in the field=value format for (JFieldVar fieldVar : fields.values()) { if (excludes.contains(fieldVar.name()) || (fieldVar.mods().getValue() & JMod.STATIC) == JMod.STATIC) { continue; } body.add(sb.invoke("append").arg(fieldVar.name())); body.add(sb.invoke("append").arg(JExpr.lit('='))); if (fieldVar.type().isPrimitive()) { body.add(sb.invoke("append").arg(JExpr.refthis(fieldVar.name()))); } else if (fieldVar.type().isArray()) { // Only primitive arrays are supported if (!fieldVar.type().elementType().isPrimitive()) { throw new UnsupportedOperationException("Only primitive arrays are supported"); } // Leverage Arrays.toString() body.add( sb.invoke("append") .arg( JOp.cond( JExpr.refthis(fieldVar.name()).eq(JExpr._null()), JExpr.lit(""), jclass .owner() .ref(Arrays.class) .staticInvoke("toString") .arg(JExpr.refthis(fieldVar.name())) .invoke("replace") .arg(JExpr.lit('[')) .arg(JExpr.lit('{')) .invoke("replace") .arg(JExpr.lit(']')) .arg(JExpr.lit('}')) .invoke("replace") .arg(JExpr.lit(", ")) .arg(JExpr.lit(","))))); } else { body.add( sb.invoke("append") .arg( JOp.cond( JExpr.refthis(fieldVar.name()).eq(JExpr._null()), JExpr.lit(""), JExpr.refthis(fieldVar.name())))); } body.add(sb.invoke("append").arg(JExpr.lit(','))); } // Add the trailer JConditional trailerConditional = body._if( sb.invoke("charAt").arg(sb.invoke("length").minus(JExpr.lit(1))).eq(JExpr.lit(','))); trailerConditional ._then() .add( sb.invoke("setCharAt") .arg(sb.invoke("length").minus(JExpr.lit(1))) .arg(JExpr.lit(']'))); trailerConditional._else().add(sb.invoke("append").arg(JExpr.lit(']'))); body._return(sb.invoke("toString")); toString.annotate(Override.class); } private void addHashCode(JDefinedClass jclass, JsonNode node) { Map fields = removeFieldsExcludedFromEqualsAndHashCode(jclass.fields(), node); JMethod hashCode = jclass.method(JMod.PUBLIC, int.class, "hashCode"); JBlock body = hashCode.body(); JVar result = body.decl(jclass.owner().INT, "result", JExpr.lit(1)); // Incorporate each non-excluded field in the hashCode calculation for (JFieldVar fieldVar : fields.values()) { if ((fieldVar.mods().getValue() & JMod.STATIC) == JMod.STATIC) { continue; } JFieldRef fieldRef = JExpr.refthis(fieldVar.name()); JExpression fieldHash; if (fieldVar.type().isPrimitive()) { if ("long".equals(fieldVar.type().name())) { fieldHash = JExpr.cast(jclass.owner().INT, fieldRef.xor(fieldRef.shrz(JExpr.lit(32)))); } else if ("boolean".equals(fieldVar.type().name())) { fieldHash = JOp.cond(fieldRef, JExpr.lit(1), JExpr.lit(0)); } else if ("int".equals(fieldVar.type().name())) { fieldHash = fieldRef; } else if ("double".equals(fieldVar.type().name())) { JClass doubleClass = jclass.owner().ref(Double.class); JExpression longField = doubleClass.staticInvoke("doubleToLongBits").arg(fieldRef); fieldHash = JExpr.cast(jclass.owner().INT, longField.xor(longField.shrz(JExpr.lit(32)))); } else if ("float".equals(fieldVar.type().name())) { fieldHash = jclass.owner().ref(Float.class).staticInvoke("floatToIntBits").arg(fieldRef); } else { fieldHash = JExpr.cast(jclass.owner().INT, fieldRef); } } else if (fieldVar.type().isArray()) { if (!fieldVar.type().elementType().isPrimitive()) { throw new UnsupportedOperationException("Only primitive arrays are supported"); } fieldHash = jclass.owner().ref(Arrays.class).staticInvoke("hashCode").arg(fieldRef); } else { fieldHash = JOp.cond(fieldRef.eq(JExpr._null()), JExpr.lit(0), fieldRef.invoke("hashCode")); } body.assign(result, result.mul(JExpr.lit(31)).plus(fieldHash)); } // Add super.hashCode() if (!jclass._extends().fullName().equals(Object.class.getName())) { body.assign(result, result.mul(JExpr.lit(31)).plus(JExpr._super().invoke("hashCode"))); } body._return(result); hashCode.annotate(Override.class); } private Map removeFieldsExcludedFromEqualsAndHashCode( Map fields, JsonNode node) { Map filteredFields = new HashMap<>(fields); JsonNode properties = node.get("properties"); if (properties != null) { if (node.has("excludedFromEqualsAndHashCode")) { JsonNode excludedArray = node.get("excludedFromEqualsAndHashCode"); for (Iterator iterator = excludedArray.elements(); iterator.hasNext(); ) { String excludedPropertyName = iterator.next().asText(); JsonNode excludedPropertyNode = properties.get(excludedPropertyName); filteredFields.remove( ruleFactory .getNameHelper() .getPropertyName(excludedPropertyName, excludedPropertyNode)); } } for (Iterator> iterator = properties.fields(); iterator.hasNext(); ) { Map.Entry entry = iterator.next(); String propertyName = entry.getKey(); JsonNode propertyNode = entry.getValue(); if (propertyNode.has("excludedFromEqualsAndHashCode") && propertyNode.get("excludedFromEqualsAndHashCode").asBoolean()) { filteredFields.remove( ruleFactory.getNameHelper().getPropertyName(propertyName, propertyNode)); } } } return filteredFields; } private void addEquals(JDefinedClass jclass, JsonNode node) { Map fields = removeFieldsExcludedFromEqualsAndHashCode(jclass.fields(), node); JMethod equals = jclass.method(JMod.PUBLIC, boolean.class, "equals"); JVar otherObject = equals.param(Object.class, "other"); JBlock body = equals.body(); body._if(otherObject.eq(JExpr._this()))._then()._return(JExpr.TRUE); body._if(otherObject._instanceof(jclass).eq(JExpr.FALSE))._then()._return(JExpr.FALSE); JVar rhsVar = body.decl(jclass, "rhs").init(JExpr.cast(jclass, otherObject)); JExpression result = JExpr.lit(true); // First, check super.equals(other) if (!jclass._extends().fullName().equals(Object.class.getName())) { result = result.cand(JExpr._super().invoke("equals").arg(rhsVar)); } // Chain the results of checking all other fields for (JFieldVar fieldVar : fields.values()) { if ((fieldVar.mods().getValue() & JMod.STATIC) == JMod.STATIC) { continue; } JFieldRef thisFieldRef = JExpr.refthis(fieldVar.name()); JFieldRef otherFieldRef = JExpr.ref(rhsVar, fieldVar.name()); JExpression fieldEquals; if (fieldVar.type().isPrimitive()) { if ("double".equals(fieldVar.type().name())) { JClass doubleClass = jclass.owner().ref(Double.class); fieldEquals = doubleClass .staticInvoke("doubleToLongBits") .arg(thisFieldRef) .eq(doubleClass.staticInvoke("doubleToLongBits").arg(otherFieldRef)); } else if ("float".equals(fieldVar.type().name())) { JClass floatClass = jclass.owner().ref(Float.class); fieldEquals = floatClass .staticInvoke("floatToIntBits") .arg(thisFieldRef) .eq(floatClass.staticInvoke("floatToIntBits").arg(otherFieldRef)); } else { fieldEquals = thisFieldRef.eq(otherFieldRef); } } else if (fieldVar.type().isArray()) { if (!fieldVar.type().elementType().isPrimitive()) { throw new UnsupportedOperationException("Only primitive arrays are supported"); } fieldEquals = jclass .owner() .ref(Arrays.class) .staticInvoke("equals") .arg(thisFieldRef) .arg(otherFieldRef); } else { fieldEquals = thisFieldRef .eq(otherFieldRef) .cor( thisFieldRef .ne(JExpr._null()) .cand(thisFieldRef.invoke("equals").arg(otherFieldRef))); } // Chain the equality of this field with the previous comparisons result = result.cand(fieldEquals); } body._return(result); equals.annotate(Override.class); } private void addInterfaces(JDefinedClass jclass, JsonNode javaInterfaces) { for (JsonNode i : javaInterfaces) { jclass._implements(resolveType(jclass._package(), i.asText())); } } private static void addGeneratedAnnotation(JDefinedClass jclass) { JClass annotationClass = jclass.owner().ref(Generated.class); JAnnotationUse generated = jclass.annotate(annotationClass); generated.param("value", "tigrisdb-model-generator"); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy