com.speedment.common.codegen.controller.AutoEquals Maven / Gradle / Ivy
Show all versions of generator-deploy Show documentation
/*
*
* 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;
}
}