##   Licensed to the Apache Software Foundation (ASF) under one
##  or more contributor license agreements.  See the NOTICE file
##  distributed with this work for additional information
##  regarding copyright ownership.  The ASF licenses this file
##  to you 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
##
##    https://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.
##
##Terminology:
##	Base class - super superclass of entity, ie, org.apache.cayenne.GenericPersistentObject or MyBaseClass
##  Super class - superclass of entity, ie,  org.apache.cayenne.art.auto._Artist
##	Sub class - class of entity, ie, org.apache.cayenne.art.Artist
##
##  Classes available in template
##    object (duplicated as 'objEntity') - the ObjEntity class: See org.apache.cayenne.map.ObjectEntity
##    stringUtils - class for string "helper" functions: See org.apache.cayenne.gen.StringUtils
##    entityUtils - class for entity "helper" functions: See org.apache.cayenne.gen.EntityUtils
##    importUtils - class for import statement management: See org.apache.cayenne.gen.ImportUtils
##    superClassName
##    superPackageName
##    subClassName
##    subPackageName
##    baseClassName
##    basePackageName
##
###################
## Create import ##
###################
${importUtils.setPackage($superPackageName)}##
${importUtils.addReservedType("${superPackageName}.${superClassName}")}##
${importUtils.addType("${basePackageName}.${baseClassName}")}##
${importUtils.addType("java.io.IOException")}##
${importUtils.addType("java.io.ObjectInputStream")}##
${importUtils.addType("java.io.ObjectOutputStream")}##
#if( $createPKProperties )
$propertyUtils.addImportForPK($entityUtils)##
#end
#foreach( $attr in ${object.DeclaredAttributes} )
$propertyUtils.addImport($attr)##
#end
#foreach( $rel in ${object.DeclaredRelationships} )
$propertyUtils.addImport($rel)##
#end
$propertyUtils.addImportForSelfProperty($object)##
${importUtils.generate()}

/**
 * Class ${superClassName} was generated by Cayenne.
 * It is probably a good idea to avoid changing this class manually,
 * since it may be overwritten next time code is regenerated.
 * If you need to make any customizations, please use subclass.
#set ( $comment = ${metadataUtils.getComment($object)} )
#if ( $comment )
 *
 * $comment
 *
#end
 */
public abstract class ${superClassName} extends ${baseClassName} {

    private static final long serialVersionUID = 1L;

###########################
## Create self property  ##
###########################
    $propertyUtils.selfPropertyDefinition($object)

###########################
## Create property names ##
###########################
#if( $createPropertyNames )
#foreach( $attr in ${object.DeclaredAttributes} )
    public static final String ${stringUtils.capitalizedAsConstant($attr.Name)}_PROPERTY = "${attr.Name}";
#end
#foreach( $rel in ${object.DeclaredRelationships} )
    public static final String ${stringUtils.capitalizedAsConstant($rel.Name)}_PROPERTY = "${rel.Name}";
#end

#end
###########################
## Create PK properties  ##
###########################
#if( $object.DbEntity )
    #foreach( $idAttr in ${object.DbEntity.PrimaryKeys} )
        #if( $createPKProperties && !${entityUtils.declaresDbAttribute($idAttr)})
            #set ( $type = "$importUtils.dbAttributeToJavaType($idAttr)")
    $propertyUtils.propertyDefinition($object, $idAttr)
        #end
    public static final String ${stringUtils.capitalizedAsConstant($idAttr.Name)}_PK_COLUMN = "${idAttr.Name}";
    #end
#end

#######################
## Create Properties ##
#######################
#foreach( $attr in ${object.DeclaredAttributes} )
    $propertyUtils.propertyDefinition($attr)
#end
#foreach( $rel in ${object.DeclaredRelationships} )
    $propertyUtils.propertyDefinition($rel)
#end

###################
## Create Fields ##
###################
#foreach( $attr in ${object.DeclaredAttributes} )
#if ($attr.Lazy)
    protected Object $stringUtils.formatVariableName(${attr.Name});
#else
#set ( $flag = $importUtils.canUsePrimitive($attr) )
#set ( $type = "$importUtils.formatJavaType(${attr.Type}, $flag)" )
    protected $type $stringUtils.formatVariableName(${attr.Name});
#end
#end

#foreach( $rel in ${object.DeclaredRelationships} )
    protected Object $stringUtils.formatVariableName(${rel.Name});
#end

#########################################################
## Create attributes and relationships set/get methods ##
#########################################################
#foreach( $attr in ${object.DeclaredAttributes} )
#set ( $name = "$stringUtils.formatVariableName(${attr.Name})")
#set ( $type = "$importUtils.formatJavaType(${attr.Type})")
##
## setter
##
#if (!${object.isReadOnly()})
    public void set${stringUtils.capitalized($attr.Name)}($type $name) {
        beforePropertyWrite("${attr.Name}", this.$name, $name);
        this.$name = $name;
    }

#end
##
## getter
##
#if ( $importUtils.isBoolean(${attr.Type}) )
	public boolean is${stringUtils.capitalized($attr.Name)}() {
#else
    public $type get${stringUtils.capitalized($attr.Name)}() {
#end
        beforePropertyRead("${attr.Name}");
#if ($attr.Lazy)
        if(this.$name instanceof Fault) {
            this.$name = ((Fault) this.$name).resolveFault(this, "$attr.Name");
        }
#end
#if ($importUtils.isPrimitive($type) && !$attr.isMandatory())
        if(this.$name == null) {
#if ($importUtils.isBoolean($type))
            return false;
#else
            return 0;
#end
        }
#end
#if ($attr.Lazy)
        return ($type)this.$name;
#else
        return this.$name;
#end
    }

#end## of foreach declared attribute
##
## Create list add/remove/get methods
#foreach( $rel in ${object.DeclaredRelationships} )
#if( $rel.ToMany )
#if ( !${object.isReadOnly()} && !$rel.ReadOnly )
    public void addTo${stringUtils.capitalized($rel.Name)}($importUtils.formatJavaType(${rel.TargetEntity.ClassName}) obj) {
        addToManyTarget("${rel.Name}", obj, true);
    }

    public void removeFrom${stringUtils.capitalized($rel.Name)}($importUtils.formatJavaType(${rel.TargetEntity.ClassName}) obj) {
        removeToManyTarget("${rel.Name}", obj, true);
    }

#end
    @SuppressWarnings("unchecked")
#if ( ${rel.CollectionType} == "java.util.Map")
    public $importUtils.formatJavaType($rel.CollectionType)<$importUtils.formatJavaType($entityUtils.getMapKeyType($rel)), $importUtils.formatJavaType($rel.TargetEntity.ClassName)> get${stringUtils.capitalized($rel.Name)}() {
        return ($importUtils.formatJavaType($rel.CollectionType)<$importUtils.formatJavaType($entityUtils.getMapKeyType($rel)), $importUtils.formatJavaType($rel.TargetEntity.ClassName)>)readProperty("${rel.Name}");
    }
#else
    public $importUtils.formatJavaType($rel.CollectionType)<$importUtils.formatJavaType($rel.TargetEntity.ClassName)> get${stringUtils.capitalized($rel.Name)}() {
        return ($importUtils.formatJavaType($rel.CollectionType)<$importUtils.formatJavaType($rel.TargetEntity.ClassName)>)readProperty("${rel.Name}");
    }
#end

#else
#if ( !${object.isReadOnly()} && !$rel.ReadOnly )
    public void set${stringUtils.capitalized($rel.Name)}($importUtils.formatJavaType(${rel.TargetEntity.ClassName}) $stringUtils.formatVariableName(${rel.name})) {
        setToOneTarget("${rel.Name}", $stringUtils.formatVariableName(${rel.name}), true);
    }

#end
    public $importUtils.formatJavaType(${rel.TargetEntity.ClassName}) get${stringUtils.capitalized($rel.Name)}() {
        return ($importUtils.formatJavaType(${rel.TargetEntity.ClassName}))readProperty("${rel.Name}");
    }

#end
#end
#############################
## Create callback methods ##
#############################
#foreach($cbname in ${entityUtils.callbackNames})
    protected abstract void ${cbname}();

#end
###########################################################
## Create writePropertyDirect/readPropertyDirect methods ##
###########################################################
    @Override
    public Object readPropertyDirectly(String propName) {
        if(propName == null) {
            throw new IllegalArgumentException();
        }

        switch(propName) {
#foreach( $attr in ${object.DeclaredAttributes} )
#set ( $name = "$stringUtils.formatVariableName(${attr.Name})")
            case "${attr.Name}":
                return this.${name};
#end
#foreach( $rel in ${object.DeclaredRelationships} )
            case "${rel.Name}":
                return this.$stringUtils.formatVariableName(${rel.name});
#end
            default:
                return super.readPropertyDirectly(propName);
        }
    }

    @Override
    public void writePropertyDirectly(String propName, Object val) {
        if(propName == null) {
            throw new IllegalArgumentException();
        }

        switch (propName) {
#foreach( $attr in ${object.DeclaredAttributes} )
#set ( $name = "$stringUtils.formatVariableName(${attr.Name})")
#set ( $flag = $importUtils.canUsePrimitive($attr) )
#set ( $type = "$importUtils.formatJavaType(${attr.Type}, $flag)")
            case "${attr.Name}":
#if ( $importUtils.isBoolean($type) )
                this.${name} = val == null ? false : ($type)val;
#elseif ($importUtils.isPrimitive($type))
                this.${name} = val == null ? 0 : ($type)val;
#else
    #if ($attr.Lazy)
                this.${name} = val;
    #else
                this.${name} = ($type)val;
    #end
#end
                break;
#end
#foreach( $rel in ${object.DeclaredRelationships} )
            case "${rel.Name}":
                this.$stringUtils.formatVariableName(${rel.name}) = val;
                break;
#end
            default:
                super.writePropertyDirectly(propName, val);
        }
    }

##################################
## Create serialization support ##
##################################
    private void writeObject(ObjectOutputStream out) throws IOException {
        writeSerialized(out);
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        readSerialized(in);
    }

    @Override
    protected void writeState(ObjectOutputStream out) throws IOException {
        super.writeState(out);
#foreach( $attr in ${object.DeclaredAttributes} )
#set ( $name = "$stringUtils.formatVariableName(${attr.Name})")
#set ( $flag = $importUtils.canUsePrimitive($attr) )
#set ( $type = "$importUtils.formatJavaType(${attr.Type}, $flag)")
#if($importUtils.isPrimitive($type))
        out.write${stringUtils.capitalized($type)}(this.$name);
#else
        out.writeObject(this.${name});
#end
#end
#foreach( $rel in ${object.DeclaredRelationships} )
        out.writeObject(this.${stringUtils.formatVariableName($rel.Name)});
#end
    }

    @Override
    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
        super.readState(in);
#foreach( $attr in ${object.DeclaredAttributes} )
#set ( $name = "$stringUtils.formatVariableName(${attr.Name})")
#set ( $flag = $importUtils.canUsePrimitive($attr) )
#set ( $type = "$importUtils.formatJavaType(${attr.Type}, $flag)")
#if($attr.Lazy)
        this.$name = in.readObject();
#elseif($importUtils.isPrimitive($type))
        this.$name = in.read${stringUtils.capitalized($type)}();
#else
        this.$name = ($type)in.readObject();
#end
#end
#foreach( $rel in ${object.DeclaredRelationships} )
#set ( $name = "${stringUtils.formatVariableName($rel.Name)}")
        this.$name = in.readObject();
#end
    }

}
