<commit_before>/*
  * Copyright 2012 <NAME>
  *
  * 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 de.jpaw.bonaparte.jpa.dsl.generator.java

import de.jpaw.bonaparte.jpa.dsl.bDDL.EntityDefinition
import de.jpaw.bonaparte.dsl.generator.DataCategory
import static extension de.jpaw.bonaparte.dsl.generator.XUtil.*
import static extension de.jpaw.bonaparte.jpa.dsl.generator.YUtil.*
import de.jpaw.bonaparte.dsl.bonScript.FieldDefinition
import de.jpaw.bonaparte.dsl.generator.DataTypeExtension
import de.jpaw.bonaparte.dsl.bonScript.ClassDefinition
import java.util.List
import de.jpaw.bonaparte.jpa.dsl.generator.PrimaryKeyType
import de.jpaw.bonaparte.dsl.generator.java.JavaCompare

class EqualsHash {
    /////////////////////////////////////////////////////////////////////
    // hashCode
    /////////////////////////////////////////////////////////////////////

    def private static hashSub33(FieldDefinition i) {
        val myIndexList = i.indexList
        return '''
            «IF myIndexList !== null»
                «myIndexList.map[i.name + it].map['''(«it» == null ? 0 : «it».hashCode())'''].join('\n+ ')»
            «ELSE»
                («i.name» == null ? 0 : «i.name».hashCode())
            «ENDIF»
        '''
    }

    def private static writeHashExpressionForSingleField(FieldDefinition i, DataTypeExtension ref) {
        if (ref.isPrimitive) {
            if (i.isArray !== null)
                return '''(«i.name» == null ? 0 : Arrays.hashCode(«i.name»))'''
            else if (i.aggregate)  // List, Map, Set
                return i.hashSub33
            else {
                // a single primitive type....
                return JavaCompare.writePrimitiveSimpleHash(i, ref)
            }
        } else {
            if (i.isArray !== null)
                return '''(«i.name» == null ? 0 : Arrays.deepHashCode(«i.name»))'''
            else if (i.aggregate)  // List, Map, Set
                return i.hashSub33
            else {
                // a single non-primitive type (Boxed or Joda or Date?)....
                if (ref.javaType == "BonaPortable")
                    // special treatment required, again! (but not for ByteArray, we do this with usertypes now as well...)
                    return '''(«i.name» == null ? 0 : Arrays.hashCode(«i.name»))'''   // straightforward recursion
                else
                    return JavaCompare.writeNonPrimitiveSimpleHash(i, ref)
            }
        }
    }

    def private static writeHashSubForListOfFields(List<FieldDefinition> fields) '''
        «IF fields !== null»
            «FOR i:fields»
                _hash = 29 * _hash + «writeHashExpressionForSingleField(i, DataTypeExtension::get(i.datatype))»;
            «ENDFOR»
        «ENDIF»
    '''
    def private static CharSequence writeHashSubForAllClassFields(ClassDefinition d) '''
        «d.extendsClass?.classRef?.writeHashSubForAllClassFields»
        «d.fields.writeHashSubForListOfFields»
    '''

    def public static writeHashMethodForClassPlusExtraFields(ClassDefinition cls, List<FieldDefinition> fields) '''
        @Override
        public int hashCode() {
            int _hash = 997;
            «cls?.writeHashSubForAllClassFields»
            «fields?.writeHashSubForListOfFields»
            return _hash;
        }
    '''

    /////////////////////////////////////////////////////////////////////
    // combined equals and hashCode (for entities embeddable key)
    /////////////////////////////////////////////////////////////////////

    def private static writeEqualsAndHashCodeForEmbeddable(EntityDefinition e, String name) '''
        @Override
        public int hashCode() {
            return «name» == null ? -1 : «name».hashCode();
        }
        @Override
        public boolean equals(Object _that) {
            if (this == _that)
                return true;
            if (_that == null || getClass() != _that.getClass())
                return false;
            return «name» != null && «name».equals(((«e.name»)_that).«name»);
        }
    '''

    /////////////////////////////////////////////////////////////////////
    // equals
    /////////////////////////////////////////////////////////////////////

    // equals delegator to ensure correct type. anything below is using the appropriate type
    def private static writeEqualsDelegator(EntityDefinition e, CharSequence codeToInsert) '''
        @Override
        public boolean equals(Object _that) {
            if (this == _that)
                return true;
            if (_that == null || getClass() != _that.getClass())  // !(_that instanceof «e.name») (take care of proxies?)
                return false;
            return equalsSub(_that);
        }
        «IF e.extendsClass !== null»
        @Override
        «ENDIF»
        protected boolean equalsSub(Object _that) {
            «e.name» __that = («e.name»)_that;
            «codeToInsert»
        }
    '''

    /** Creates a comparison of two scalar fields. The first is known not to be null. Note this is different from the BON comparison due to serialized fields. */
    def private static writeBasicCompare(FieldDefinition i, String index) {
        switch (getJavaDataType(i.datatype)) {
        case "BonaPortable":        '''Arrays.equals(«index», __that.«index»)'''  // mapped to byte [] (support for serialized storage)
        case "byte []":             '''Arrays.equals(«index», __that.«index»)'''
        case "BigDecimal":          '''«index».compareTo(__that.«index») == 0'''  // we want the comparison to be "true" if the values are the same on the database, which ignores the actual presentation and corresponds to compareTo
//        case "Instant":             '''«index».compareTo(__that.«index») == 0'''  // mapped to Calendar or Date or using userdata fields
//        case "LocalTime":           '''«index».compareTo(__that.«index») == 0'''  // mapped to Calendar or Date or using userdata fields
//        case "LocalDate":           '''«index».compareTo(__that.«index») == 0'''  // mapped to Calendar or Date or using userdata fields
//        case "LocalDateTime":       '''«index».compareTo(__that.«index») == 0'''  // mapped to Calendar or Date or using userdata fields
        // case "Double":      '''«index».compareTo(«tindex») == 0''' // difference to equals is for NaN values
        // case "Float":       '''«index».compareTo(«tindex») == 0''' // difference to equals is for NaN values
        default:                    '''«index».equals(__that.«index»)'''
        }
    }


    // only caller in next method
    def private static writeCompareStuff(FieldDefinition i, String a) {
        val ref = DataTypeExtension::get(i.datatype)
        val b = "__that." + a
        if (ref.category == DataCategory::OBJECT)
            return JavaCompare.doCompareWithNull(a, b, a + ".equals(" + b + ")")
        else if (ref.isPrimitive)
            return '''«a» == «b»'''
        else
            return JavaCompare.doCompareWithNull(a, b, writeBasicCompare(i, a))
    }

    // main entry to write the code - multiple different callers.
    // __that holds an object of the same type and is not null
    def private static writeEqualsSubForListOfFields(List<FieldDefinition> l) '''
        «FOR i: l»
            «IF i.isArray !== null»<commit_msg>same fix for JPA-DSL<commit_after>/*
  * Copyright 2012 <NAME>
  *
  * 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 de.jpaw.bonaparte.jpa.dsl.generator.java

import de.jpaw.bonaparte.jpa.dsl.bDDL.EntityDefinition
import de.jpaw.bonaparte.dsl.generator.DataCategory
import static extension de.jpaw.bonaparte.dsl.generator.XUtil.*
import static extension de.jpaw.bonaparte.jpa.dsl.generator.YUtil.*
import de.jpaw.bonaparte.dsl.bonScript.FieldDefinition
import de.jpaw.bonaparte.dsl.generator.DataTypeExtension
import de.jpaw.bonaparte.dsl.bonScript.ClassDefinition
import java.util.List
import de.jpaw.bonaparte.jpa.dsl.generator.PrimaryKeyType
import de.jpaw.bonaparte.dsl.generator.java.JavaCompare

class EqualsHash {
    /////////////////////////////////////////////////////////////////////
    // hashCode
    /////////////////////////////////////////////////////////////////////

    def private static hashSub33(FieldDefinition i) {
        val myIndexList = i.indexList
        return '''
            «IF myIndexList !== null»
                «myIndexList.map[i.name + it].map['''(«it» == null ? 0 : «it».hashCode())'''].join('\n+ ')»
            «ELSE»
                («i.name» == null ? 0 : «i.name».hashCode())
            «ENDIF»
        '''
    }

    def private static writeHashExpressionForSingleField(FieldDefinition i, DataTypeExtension ref) {
        if (ref.isPrimitive) {
            if (i.isArray !== null)
                return '''(«i.name» == null ? 0 : Arrays.hashCode(«i.name»))'''
            else if (i.aggregate)  // List, Map, Set
                return i.hashSub33
            else {
                // a single primitive type....
                return JavaCompare.writePrimitiveSimpleHash(i, ref)
            }
        } else {
            if (i.isArray !== null)
                return '''(«i.name» == null ? 0 : Arrays.deepHashCode(«i.name»))'''
            else if (i.aggregate)  // List, Map, Set
                return i.hashSub33
            else {
                // a single non-primitive type (Boxed or Joda or Date?)....
                if (ref.javaType == "BonaPortable")
                    // special treatment required, again! (but not for ByteArray, we do this with usertypes now as well...)
                    return '''(«i.name» == null ? 0 : Arrays.hashCode(«i.name»))'''   // straightforward recursion
                else
                    return JavaCompare.writeNonPrimitiveSimpleHash(i, ref)
            }
        }
    }

    def private static writeHashSubForListOfFields(List<FieldDefinition> fields) '''
        «IF fields !== null»
            «FOR i:fields»
                _hash = 29 * _hash + «writeHashExpressionForSingleField(i, DataTypeExtension::get(i.datatype))»;
            «ENDFOR»
        «ENDIF»
    '''
    def private static CharSequence writeHashSubForAllClassFields(ClassDefinition d) '''
        «d.extendsClass?.classRef?.writeHashSubForAllClassFields»
        «d.fields.writeHashSubForListOfFields»
    '''

    def public static writeHashMethodForClassPlusExtraFields(ClassDefinition cls, List<FieldDefinition> fields) '''
        @Override
        public int hashCode() {
            int _hash = 997;
            «cls?.writeHashSubForAllClassFields»
            «fields?.writeHashSubForListOfFields»
            return _hash;
        }
    '''

    /////////////////////////////////////////////////////////////////////
    // combined equals and hashCode (for entities embeddable key)
    /////////////////////////////////////////////////////////////////////

    def private static writeEqualsAndHashCodeForEmbeddable(EntityDefinition e, String name) '''
        @Override
        public int hashCode() {
            return «name» == null ? -1 : «name».hashCode();
        }
        @Override
        public boolean equals(Object _that) {
            if (this == _that)
                return true;
            if (_that == null || getClass() != _that.getClass())
                return false;
            return «name» != null && «name».equals(((«e.name»)_that).«name»);
        }
    '''

    /////////////////////////////////////////////////////////////////////
    // equals
    /////////////////////////////////////////////////////////////////////

    // equals delegator to ensure correct type. anything below is using the appropriate type
    def private static writeEqualsDelegator(EntityDefinition e, CharSequence codeToInsert) '''
        @Override
        public boolean equals(Object _that) {
            if (this == _that)
                return true;
            if (_that == null || getClass() != _that.getClass())  // !(_that instanceof «e.name») (take care of proxies?)
                return false;
            return equalsSub(_that);
        }
        «IF e.extendsClass !== null»
        @Override
        «ENDIF»
        protected boolean equalsSub(Object _that) {
            «e.name» __that = («e.name»)_that;
            «codeToInsert»
        }
    '''

    /** Creates a comparison of two scalar fields. The first is known not to be null. Note this is different from the BON comparison due to serialized fields. */
    def private static writeBasicCompare(FieldDefinition i, String index) {
        switch (getJavaDataType(i.datatype)) {
        case "BonaPortable":        '''Arrays.equals(«index», __that.«index»)'''  // mapped to byte [] (support for serialized storage)
        case "byte []":             '''Arrays.equals(«index», __that.«index»)'''
        case "BigDecimal":          '''(__that.«index» != null && «index».compareTo(__that.«index») == 0)'''  // we want the comparison to be "true" if the values are the same on the database, which ignores the actual presentation and corresponds to compareTo
//        case "Instant":             '''«index».compareTo(__that.«index») == 0'''  // mapped to Calendar or Date or using userdata fields
//        case "LocalTime":           '''«index».compareTo(__that.«index») == 0'''  // mapped to Calendar or Date or using userdata fields
//        case "LocalDate":           '''«index».compareTo(__that.«index») == 0'''  // mapped to Calendar or Date or using userdata fields
//        case "LocalDateTime":       '''«index».compareTo(__that.«index») == 0'''  // mapped to Calendar or Date or using userdata fields
        // case "Double":      '''«index».compareTo(«tindex») == 0''' // difference to equals is for NaN values
        // case "Float":       '''«index».compareTo(«tindex») == 0''' // difference to equals is for NaN values
        default:                    '''«index».equals(__that.«index»)'''
        }
    }


    // only caller in next method
    def private static writeCompareStuff(FieldDefinition i, String a) {
        val ref = DataTypeExtension::get(i.datatype)
        val b = "__that." + a
        if (ref.category == DataCategory::OBJECT)
            return JavaCompare.doCompareWithNull(a, b, a + ".equals(" + b + ")")
        else if (ref.isPrimitive)
            return '''«a» == «b»'''
        else
            return JavaCompare.doCompareWithNull(a, b, writeBasicCompare(i, a))
    }

    // main entry to write the code - multiple different callers.
    // __that holds an object of the same type and is not null
    def private static writeEqualsSubForListOfFields(List<FieldDefinition> l) '''
        «FOR i: l»
            «IF i.isArray !== null»
{'fraction_non_alphanumeric': 0.16661371290589058, 'fraction_numerical': 0.004066848827603736, 'mean_word_length': 1.9015486725663717, 'pattern_counts': {'":': 18, '<': 11, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4268328', 'n_tokens_mistral': 4268, 'n_tokens_neox': 4140, 'n_words': 1508}