Announcement Announcement Module
Collapse
No announcement yet.
SDN Can't override final RelationshipBacked.hashCode(), equals(java.lang.Object) Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • SDN Can't override final RelationshipBacked.hashCode(), equals(java.lang.Object)

    Hi,
    I am trying to implement some clone functionality for SDN Entities with advanced mapping (aspectj compile time weaving). In my test scenarios I am creating new nodes from existing ones. I need to override public int hashCode() and public boolean equals(Object obj) methods in entities. Unit test results are OK within Eclipse project with AJDT nature (in attachment), but running it by maven (mvn clean test) throws aspectj compiler exception :

    Code:
    INFO] --- aspectj-maven-plugin:1.4:compile (default) @ test ---
    ERROR] can't override final int org.springframework.data.neo4j.aspects.core.RelationshipBacked.hashCode()
    ERROR] can't override final boolean org.springframework.data.neo4j.aspects.core.RelationshipBacked.equals(java.lang.Objec
    t)
    When I try to move hashCode() and equals(Object obj) methods to abstract class level, then there is another exceptions:

    Code:
    [INFO] --- aspectj-maven-plugin:1.4:compile (default) @ test ---
    [ERROR] inter-type declaration from org.springframework.data.neo4j.aspects.support.relationship.Neo4jRelationshipBacking
    conflicts with existing member: boolean user.GraphEntity.equals(java.lang.Object)
    [ERROR] inter-type declaration from org.springframework.data.neo4j.aspects.support.relationship.Neo4jRelationshipBacking
    conflicts with existing member: int user.GraphEntity.hashCode()
    [ERROR] inter-type declaration from org.springframework.data.neo4j.aspects.support.node.Neo4jNodeBacking conflicts with e
    xisting member: boolean user.GraphEntity.equals(java.lang.Object)
    [ERROR] inter-type declaration from org.springframework.data.neo4j.aspects.support.node.Neo4jNodeBacking conflicts with e
    xisting member: int user.GraphEntity.hashCode()
    [ERROR] inter-type declaration from org.springframework.data.neo4j.aspects.support.relationship.Neo4jRelationshipBacking
    conflicts with existing member: int user.GraphEntity.hashCode()
    [ERROR] inter-type declaration from org.springframework.data.neo4j.aspects.support.node.Neo4jNodeBacking conflicts with e
    xisting member: int user.GraphEntity.hashCode()
    [ERROR] inter-type declaration from org.springframework.data.neo4j.aspects.support.relationship.Neo4jRelationshipBacking
    conflicts with existing member: boolean user.GraphEntity.equals(java.lang.Object)
    [ERROR] inter-type declaration from org.springframework.data.neo4j.aspects.support.node.Neo4jNodeBacking conflicts with e
    xisting member: boolean user.GraphEntity.equals(java.lang.Object)
    Please make that methods non final if you have not another reason.
    Or maybe there is anothor solution for this clone use case.

    Code:
    	@Test
    	public void cloneUser(){
    		User findedUser = userDao.findByPropertyValue("userName", "a");
    		assertThat(findedUser.getId(), notNullValue());
    		User clone = (User) findedUser.clone();
    		assertThat(findedUser, equalTo(clone));
    		assertThat(findedUser.getId(), equalTo(clone.getId()));
    		assertThat(findedUser.getUserName(), equalTo(clone.getUserName()));
    		clone.setUserName("x");
    		clone.setId(null);
    		assertThat(clone.getId(), nullValue());
    		User savedClone = userDao.save(clone);
    		assertThat(savedClone.getId(), notNullValue());
    		assertThat(savedClone.getUserName(), equalTo(clone.getUserName()));
    		assertThat(findedUser, not(equalTo(savedClone)));
    		assertThat(findedUser.getId(), not(equalTo(savedClone.getId())));
    	}
    Thanks
    Vlado K.

  • #2
    Vlado,

    I can't tell you why those methods are final, or if they can be made non-final. We will have to look at it some more. In the mean time, could you show us what your User class (and any super/ subclasses) looks like please?

    In the short term, you will need a workaround. Perhaps a wrapper for User that implements equals and hashcode, and delegates calls to the User object?

    Regards,
    Lasse

    Comment


    • #3
      What is the use-case for overriding hashCode and equals? Or cloning the entities in the first place?

      Perhaps what you really want to do is to create a separate class that is use-case specific and contains only the data you need (which might be fetched even from deeper in the graph).

      After all the entities are live view of the database, mapped to the appropriate node and relationship.

      That's why the hashCode & equals check the node & rel-id for equality and nothing else. Overriding them might break a lot of behavior in your system and the infrastructure.

      Comment


      • #4
        Hi Guys,
        I have common create copy functionality for every entity in application. There are many jpa entities in which equals and hash methods are based on primaryKey (long or composite key). So create copy means creating clone instance, setting primary key to null and then save (entitymanager.persist()). New copied instance has new primaryKey with properties equal to original one. The same thing I need with SDN entities. I simulate it in this unit test.

        Code:
        	public void cloneUser(){
        		User findedUser = userDao.findByPropertyValue("userName", "a");
        		assertThat(findedUser.getId(), notNullValue());
        		User clone = (User) findedUser.clone();
        		assertThat(findedUser, not(sameInstance(clone)));
        		assertThat(findedUser, equalTo(clone));
        		assertThat(findedUser.getId(), equalTo(clone.getId()));
        		assertThat(findedUser.getUserName(), equalTo(clone.getUserName()));
        		clone.setUserName("x");
        		clone.setId(null);
        		assertThat(clone.getId(), nullValue());
        		User savedClone = userDao.save(clone);
        		assertThat(savedClone.getId(), notNullValue());
        		assertThat(savedClone.getUserName(), equalTo(clone.getUserName()));
        		assertThat(findedUser, not(sameInstance(savedClone)));
        		assertThat(findedUser, not(equalTo(savedClone)));
        		assertThat(findedUser.getId(), not(equalTo(savedClone.getId())));
        	}
        My first attempt was to implement only clone() method and setter for id() by calling setPersistentState(null);.

        Code:
        package user;
        
        import org.springframework.data.neo4j.annotation.Indexed;
        import org.springframework.data.neo4j.annotation.NodeEntity;
        
        @NodeEntity
        public class User implements Cloneable {
        	
        	@Indexed(unique=true)
        	String userName;
        	
        	public Long getId() {
        		return getNodeId();
        	}
        
        	public void setId(Long id) {
        		if (id == null){
        			setPersistentState(null);
        		}
        	}
        	
        	public String getUserName() {
        		return userName;
        	}
        
        	public void setUserName(String userName) {
        		this.userName = userName;
        	}
        
        	@Override
        	public User clone() {
        		try {
        			return (User) super.clone();
        		} catch (CloneNotSupportedException e) {
        			return null;
        		}
        	}
        
        }
        In this case there is error on this line :
        assertThat(findedUser, not(sameInstance(savedClone)));

        java.lang.AssertionError:
        Expected: not sameInstance(<user.User@5>)
        got: <user.User@5>

        Clone entity is still the same instance when i call dao.save().

        Next I was trying to rewrite clone method by creating empty new instance without reference to backing node and copy the properties from original. Because I think and Michael said that the hash and equals are based only on node id, i must define @GraphId id property.

        Code:
        package user;
        
        import org.springframework.data.neo4j.annotation.GraphId;
        import org.springframework.data.neo4j.annotation.Indexed;
        import org.springframework.data.neo4j.annotation.NodeEntity;
        
        @NodeEntity
        public class User implements Cloneable {
        	
        	@GraphId
        	Long id;
        
        	@Indexed(unique=true)
        	String userName;
        	
        	public Long getId() {
        		return id;
        	}
        
        	public void setId(Long id) {
        		this.id = id;
        	}
        	
        	public String getUserName() {
        		return userName;
        	}
        
        	public void setUserName(String userName) {
        		this.userName = userName;
        	}
        
        	@Override
        	public User clone() {
        		User clone = new User();
        		clone.setId(this.getId());
        		clone.setUserName(this.getUserName());
        		return clone;
        	}
        }
        In this case error was on line :
        assertThat(findedUser, equalTo(clone));

        java.lang.AssertionError:
        Expected: <user.User@6e3e5e>
        got: <user.User@1>

        That was the reason why I need to override equals and hash methods.
        This version of User passing through unit test.

        Code:
        package user;
        
        import org.apache.commons.lang.builder.EqualsBuilder;
        import org.apache.commons.lang.builder.HashCodeBuilder;
        import org.springframework.data.neo4j.annotation.GraphId;
        import org.springframework.data.neo4j.annotation.Indexed;
        import org.springframework.data.neo4j.annotation.NodeEntity;
        
        @NodeEntity
        public class User implements Cloneable {
        	
        	@GraphId
        	Long id;
        
        	@Indexed(unique=true)
        	String userName;
        	
        	public Long getId() {
        		return id;
        	}
        
        	public void setId(Long id) {
        		this.id = id;
        	}
        	
        	public String getUserName() {
        		return userName;
        	}
        
        	public void setUserName(String userName) {
        		this.userName = userName;
        	}
        
        	@Override
        	public int hashCode() {
        		return new HashCodeBuilder(3, 5).append(getId()).toHashCode();
        	}
        
        	@Override
        	public boolean equals(Object obj) {
        		if (this == obj) {
        			return true;
        		}
        		if (obj == null || !this.getClass().isAssignableFrom(obj.getClass())) {
        			return false;
        		}
        		User that = (User) obj;
        		if (getId() == null && that.getId() == null) {
        			return this == obj;
        		}
        		return new EqualsBuilder()
        				.append(getId(), that.getId())
        				.isEquals();
        	}
        
        	@Override
        	public User clone() {
        		User clone = new User();
        		clone.setId(this.getId());
        		clone.setUserName(this.getUserName());
        		return clone;
        	}
        }
        This kind of clone() implementation I use successfully on all SDN entities but only on @RelationshipEntitys there is build error during maven build because aspect defines final equals and hash methods. Eclipse has no problem because there is maybe different build phase. My first post on this thread was about this issue. Sample code and unit test (little bit different) are in first attachment.
        Thanks
        Vlado

        Comment


        • #5
          Vlado,

          If I understand your use case correctly, you want to use existing nodes from the database as prototypes for new nodes?

          Doesn't that then mean you can implement clone like so:

          Code:
          @Override
          	public User clone() {
          		User clone = new User();
          		clone.setUserName(this.getUserName());
          		return clone;
          	}
          And your test becomes:

          Code:
          @Test
          	public void cloneUser(){
          		User foundUser = userDao.findByPropertyValue("userName", "a");
          
          		User clone = (User) foundUser.clone();
          		assertThat(clone.getId(), equalTo(null));
          		assertThat(foundUser.getUserName(), equalTo(clone.getUserName()));
          
          		clone.setUserName("x");
          		User savedClone = userDao.save(clone);
          
          		assertThat(savedClone.getId(), notNullValue());
          		assertThat(savedClone.getUserName(), equalTo(clone.getUserName()));
          		assertThat(foundUser.getId(), not( equalTo( savedClone.getId() ) ));
          	}
          Now, your User entity will have a provided equals/ hashcode pair that only looks at identity - do you really need anything more? If so, a wrapper might be a good idea.

          Regards,

          Lasse

          Comment


          • #6
            Hi Lasse,
            you understood right my use case. I think that x.clone().equals(x) must be true but javadoc says that this is not an absolute requirement. For that standalone use case I can use your clone() version.

            Thanks
            Vlado

            Comment

            Working...
            X