Search code examples
reflectionmetamodelceylon

Ceylon metamodel


I am studying Ceylon and have question about it metamodel. I want to create some create some base class 'DataContainer' which allow to instantiate immutable classes with build-in equals-hash implementation: e.g. Identifier(125, "ab") == Identifier(125, "ab") So base class should collect all shared non-variable values and use this information in 'hash' an 'equals' methods. I have wrote this code:

shared abstract class DataContainer(ClassDeclaration declaration) {
    value members = {
        for (i in declaration.memberDeclarations<ValueDeclaration>())
            if (!i.variable, i.name != "hash", i.name != "string") i
    };
    variable Integer? hashCode = null;

    shared actual Boolean equals(Object that) {
        if (is DataContainer that) {
            for (item in members) {
                value thisMember = item.memberGet(this);
                value thatMember = item.memberGet(that);
                if (exists thisMember, exists thatMember) {
                    if (thisMember != thatMember) { return false; }
                } else if (thisMember exists != thatMember exists) { return false; }
            }
            return true;
        }
        return false;
    }

    shared actual Integer hash => hashCode else (hashCode = calculateHash());

    Integer calculateHash() {
        variable value result = 0;
        for(i in members) {
            if (exists member = i.memberGet(this)) {
                result = result.xor(member.hash);
            }
        }
        return result;
    }
}

class Identifier(shared Integer? id, shared String? name) extends DataContainer(`class`) {}

The Identifier class is the client of DataContainer. I like this solution in whole but I have to pass 'class' into the super class constructor because if I use 'class' inside DataContainer it doesn't see any members of subclass. How can I obtain actual list of extended class's members in base class methods? Something like 'this' doesn't work...


Solution

  • I found solution thanks to guys from Ceylon community. The function classDeclaration(this) from ceylon.language.meta should be used instead of 'class'.

    This is the final code:

    shared abstract class DataContainer() {
        variable Integer? _hash = null;
        variable ValueDeclaration[]? _members = null;
    
        shared actual Boolean equals(Object that) {
            if (is DataContainer that) {
                for (i in members) {
                    value thisMember = i.memberGet(this);
                    value thatMember = i.memberGet(that);
                    if (exists thisMember, exists thatMember) {
                        if (thisMember != thatMember) { return false; }
                    } else if (thisMember exists != thatMember exists) { return false; }
                }
                return true;
            }
            return false;
        }
    
        shared actual Integer hash => _hash else (_hash = calculateHash());
    
        ValueDeclaration[] members => _members else (_members = [
            for (i in classDeclaration(this).memberDeclarations<ValueDeclaration>())
                if (!i.variable, i.name != "string", i.name != "hash") i
        ]);
    
        Integer calculateHash() {
            variable Integer result = 0;
            for (i in members) {
                if (exists member = i.memberGet(this)) {
                    result = result.xor(member.hash);
                }
            }
            return result;
        }
    }
    
    class Identifier(shared Integer? number, shared String? name) extends DataContainer() {}