class AutoEquals<T extends HasFields<T> & HasMethods<T> & HasName<T>> 
implements Consumer<T> {
    
    protected final HasImports<?> importer;
    protected static final String EQUALS = "equals";
    protected static final String HASHCODE = "hashCode";
    
    /**
     * Instantiates the <code>AutoEquals</code> 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 <code>equals()</code> and a <code>hashCode()</code> method to 
     * the specified model.
     * <p>
     * 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 <code>equals()</code>-part of the <code>accept</code> 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<Type> supertype = ((HasSupertype<?>) model).getSupertype();
                    if (supertype.isPresent()) {
                        m.add(Formatting.tab() + ".filter(o -> super.equals(o))");
                    }
                }
            })
            .add(Formatting.tab() + ".filter(o -> getClass().equals(o.getClass()))")
            .add(Formatting.tab() + ".map(o -> (" + model.getName() + ") o)")
            .add(Formatting.tab() + model.getFields().stream().map(this::compare).collect(
                    Collectors.joining(Formatting.nl() + Formatting.tab())
                ))
            .add(Formatting.tab() + ".isPresent();")
        );
        
    }
    
    /**
     * The <code>hashCode()</code>-part of the <code>accept</code> 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(Formatting.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 <code>true</code> if the specified type is a primitive type.
     * 
     * @param type  the type
     * @return      <code>true</code> if primitive, else <code>false</code>
     */
    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 if 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        <code>true</code> if found, else <code>false</code>
     */
    protected boolean hasMethod(T model, String method, int params) {<FILL_FUNCTION_BODY>}
}

        requireNonNull(model);
        requireNonNull(method);
        requireNonNull(params);
        for (Method m : model.getMethods()) {
            if (method.equals(m.getName()) && m.getFields().size() == params) {
                return true;
            }
        }
        return false;
    
<no_super_class>