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

com.speedment.common.codegen.controller.AutoEquals Maven / Gradle / Ivy

Go to download

A Speedment bundle that shades all dependencies into one jar. This is useful when deploying an application on a server.

The newest version!
/*
 *
 * Copyright (c) 2006-2019, Speedment, Inc. All Rights Reserved.
 *
 * 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.speedment.common.codegen.controller;

import com.speedment.common.codegen.model.*;
import com.speedment.common.codegen.model.trait.*;

import java.lang.reflect.Type;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static com.speedment.common.codegen.constant.DefaultAnnotationUsage.OVERRIDE;
import static com.speedment.common.codegen.constant.DefaultJavadocTag.PARAM;
import static com.speedment.common.codegen.constant.DefaultJavadocTag.RETURN;
import static com.speedment.common.codegen.util.Formatting.nl;
import static com.speedment.common.codegen.util.Formatting.tab;
import static java.util.Objects.requireNonNull;

/**
 * This control can be applied on a class, enum or similar to auto generate
 * an equals and a hashCode method. The control uses
 * all the fields to determine the salt.
 * 

* The control must be instantiated with the {@link File} as a parameter. * The reason for this is that the generated methods might require new * dependencies to be imported. *

* To use AutoEquals, follow this example: *

 *     file.add(
 *         Class.of("Vector2f")
 *             .add(Field.of("x", FLOAT_PRIMITIVE)
 *                 .public_()
 *                 .set(new NumberValue(0)))
 * 
 *             .add(Field.of("y", FLOAT_PRIMITIVE)
 *                 .public_()
 *                 .set(new NumberValue(0)))
 * 
 *             .call(new AutoEquals<>(file))
 *     );
 * 
*

* If one of the methods already exists, it will not be overwritten. * * @author Emil Forslund * @param The extending type */ public final class AutoEquals & HasMethods & HasName> implements Consumer { protected final HasImports importer; protected static final String EQUALS = "equals", HASHCODE = "hashCode"; /** * Instantiates the AutoEquals using something that imports * can be added to. This can for an example be a {@link File}. * * @param importer the importer */ public AutoEquals(HasImports importer) { this.importer = requireNonNull(importer); } /** * Adds an equals() and a hashCode() method to * the specified model. *

* If one of the methods already exists, it will not be overwritten. * * @param model the model */ @Override public void accept(T model) { requireNonNull(model); if (!hasMethod(model, EQUALS, 1)) { acceptEquals(model); } if (!hasMethod(model, HASHCODE, 0)) { acceptHashcode(model); } } /** * The equals()-part of the accept method. * * @param model the model */ protected void acceptEquals(T model) { requireNonNull(model); if (importer != null) { importer.add(Import.of(Objects.class)); importer.add(Import.of(Optional.class)); } model.add(Method.of(EQUALS, boolean.class) .set( Javadoc.of( "Compares this object with the specified one for equality. " + "The other object must be of the same type and not null for " + "the method to return true." ) .add(PARAM.setValue("other").setText("The object to compare with.")) .add(RETURN.setText("True if the objects are equal.")) ).public_() .add(OVERRIDE) .add(Field.of("other", Object.class)) .add("return Optional.ofNullable(other)") .call(m -> { if (HasSupertype.class.isAssignableFrom(model.getClass())) { final Optional supertype = ((HasSupertype) model).getSupertype(); if (supertype.isPresent()) { m.add(tab() + ".filter(o -> super.equals(o))"); } } }) .add(tab() + ".filter(o -> getClass().equals(o.getClass()))") .add(tab() + ".map(o -> (" + model.getName() + ") o)") .add(tab() + model.getFields().stream().map(this::compare).collect( Collectors.joining(nl() + tab()) )) .add(tab() + ".isPresent();") ); } /** * The hashCode()-part of the accept method. * * @param model the model */ protected void acceptHashcode(T model) { requireNonNull(model); model.add(Method.of(HASHCODE, int.class) .set( Javadoc.of( "Generates a hashCode for this object. If any field is " + "changed to another value, the hashCode may be different. " + "Two objects with the same values are guaranteed to have " + "the same hashCode. Two objects with the same hashCode are " + "not guaranteed to have the same hashCode." ) .add(RETURN.setText("The hash code.")) ).public_() .add(OVERRIDE) .add("int hash = 7;") .add(model.getFields().stream() .map(this::hash) .collect(Collectors.joining(nl())) ) .add("return hash;") ); } /** * Generates code for comparing the specified field in this and another * object. * * @param f the field * @return the comparing code */ protected String compare(Field f) { requireNonNull(f); final StringBuilder str = new StringBuilder(".filter(o -> "); if (isPrimitive(f.getType())) { str.append("(this.") .append(f.getName()) .append(" == o.") .append(f.getName()) .append(")"); } else { str.append("Objects.equals(this.") .append(f.getName()) .append(", o.") .append(f.getName()) .append(")"); } return str.append(")").toString(); } /** * Generates code for hashing the specified field. * * @param f the field * @return the hashing code */ protected String hash(Field f) { requireNonNull(f); final String prefix = "hash = 31 * hash + ("; final String suffix = ".hashCode(this." + f.getName() + "));"; switch (f.getType().getTypeName()) { case "byte": return prefix + "Byte" + suffix; case "short": return prefix + "Short" + suffix; case "int": return prefix + "Integer" + suffix; case "long": return prefix + "Long" + suffix; case "float": return prefix + "Float" + suffix; case "double": return prefix + "Double" + suffix; case "boolean": return prefix + "Boolean" + suffix; case "char": return prefix + "Character" + suffix; default: return prefix + "Objects" + suffix; } } /** * Returns true if the specified type is a primitive type. * * @param type the type * @return true if primitive, else false */ protected boolean isPrimitive(Type type) { requireNonNull(type); switch (type.getTypeName()) { case "byte": case "short": case "int": case "long": case "float": case "double": case "boolean": case "char": return true; default: return false; } } /** * Returns the a method with the specified signature exists. * * @param model the model * @param method the method name to look for * @param params the number of parameters in the signature * @return true if found, else false */ protected boolean hasMethod(T model, String method, int params) { requireNonNull(model); requireNonNull(method); requireNonNull(params); Method found = null; for (Method m : model.getMethods()) { if (method.equals(m.getName()) && m.getFields().size() == params) { found = m; break; } } return found != null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy