<commit_before>/*
 *  Copyright 2009-2014 <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 org.ujorm.implementation.orm;

import java.util.HashSet;
import java.util.Set;
import org.ujorm.Key;
import org.ujorm.Ujo;
import org.ujorm.UjoAction;
import org.ujorm.core.KeyFactory;
import org.ujorm.core.UjoManager;
import org.ujorm.core.annot.PackagePrivate;
import org.ujorm.extensions.Property;
import org.ujorm.implementation.quick.QuickUjo;
import org.ujorm.orm.ExtendedOrmUjo;
import org.ujorm.orm.ForeignKey;
import org.ujorm.orm.OrmKeyFactory;
import org.ujorm.orm.OrmUjo;
import org.ujorm.orm.Session;
import static org.ujorm.extensions.Property.UNDEFINED_INDEX;

/**
 * This abstract implementation of the OrmUjo interface is situable
 * for implementation the persistent entities.
 * <br>Instances of the OrmTable can be serializable, but you need to know that only
 * business data will be transferred, the session and property changes will not be passed.
 * <br>The sample of use:
 * <pre class="pre">
 *  <span class="comment">&#47;** Using INSERT *&#47;</span>
 *  <span class="keyword-directive">public</span> <span class="keyword-directive">void</span> useCreateItem() {
 *
 *      Order order = <span class="keyword-directive">new</span> Order();
 *      order.set(Order.DATE, <span class="keyword-directive">new</span> Date());
 *      order.set(Order.DESCR, <span class="character">&quot;John's order&quot;</span>);
 *
 *      Item item = <span class="keyword-directive">new</span> Item();
 *      item.set(Item.ORDER, order);
 *      item.set(Item.DESCR, <span class="character">&quot;Yellow table&quot;</span>);
 *
 *      Session session = OrmHandler.getInstance().getSession();
 *      session.save(order);
 *      session.save(item);
 *      session.commit();
 *  }</pre>
 *
 * @author <NAME>
 * @see org.ujorm.implementation.orm.RelationToMany
 * @see org.ujorm.core.UjoIterator
 */
public abstract class OrmTable<U extends OrmTable> extends QuickUjo implements ExtendedOrmUjo<U> {

    /** An empty array of the UJO keys */
    @PackagePrivate
    static final Key[] EMPTY = new Key[0];
    /** ORM session */
    transient private Session session;
    /** Set of changes */
    transient private Set<Key> changes = null;

    public OrmTable() {
    }

    /** Read a session */
    @Override
    public Session readSession() {
        return session;
    }

    /** Write a session */
    @Override
    public void writeSession(Session session) {
        this.session = session;
    }

    /** A method for an internal use only. */
    @Override
    public void writeValue(Key<?,?> key, Object value) {
        if (session!=null) {
            if (changes==null) {
                changes = new HashSet<Key>(8);
            }
            changes.add(key);
        }
        super.writeValue(key, value);
    }

    /**
     * Returns keys of changed values in a time when any <strong>session</strong> is assigned.
     * The method is used by a SQL UPDATE statement to update assigned values only.
     * Implementation tip: create a new property type of {@link Set<Key>}
     * and in the method writeValue assign the current Key always.
     * @param clear True value clears all the key changes.
     * @return Key array of the modified values.
     */
    @Override
    public Key[] readChangedProperties(boolean clear) {
        final Key[] result
            = changes==null || changes.isEmpty()
            ? EMPTY
            : changes.toArray(new Key[changes.size()])
            ;
        if (clear) {
            changes = null;
        }
        return result;
    }

    /** Getter based on Key implemeted by a pattern UjoExt */
    @SuppressWarnings("unchecked")
    public final <UJO extends U, VALUE> VALUE get(final Key<UJO, VALUE> key) {
        final VALUE result = key.of((UJO)this);
        return result;
    }

    /** Setter  based on Key. Type of value is checked in the runtime.
     * The method was implemented by a pattern UjoExt.
     */
    @SuppressWarnings({"unchecked"})
    public final <UJO extends U, VALUE> U set
        ( final Key<UJO, VALUE> key
        , final VALUE value
        ) {
        UjoManager.assertAssign(key, value);
        key.setValue((UJO)this, value);
        return (U) this;
    }

    /** Test an authorization of the action. */
    @Override
    public boolean readAuthorization(UjoAction action, Key key, Object value) {
        switch (action.getType()) {
            case UjoAction.ACTION_TO_STRING:
                return !(key instanceof RelationToMany);
            default:
                return super.readAuthorization(action, key, value);
        }
    }

    /** Read the foreign key.
     * This is useful to obtain the foreign key value without (lazy) loading the entire object.
     * If the lazy object is loaded, the method will need the Session to build the ForeignKey instance.
     * <br>NOTE: The method is designed for developers only, the Ujorm doesn't call it newer.
     * @param key Must be direct key only ({@link Key#isDirect()}==true)
     * @return If no related object is available, then the result has the NULL value.
     * @throws IllegalStateException Method throws an exception for a wrong key type.
     * @throws NullPointerException Method throws an exception if a Session is missing after a lazy initialization of the key.
     */
    @Override
    public <UJO extends U> ForeignKey readFK(Key<UJO, ? extends OrmUjo> key) throws IllegalStateException {
        final Object value = super.readValue(key);
        if (value==null || value instanceof ForeignKey) {
            return (ForeignKey) value;
        }
//      if (key instanceof RelationToOne) {
//          // TODO: fix the case the key is a relation:
//          final Key key = ((RelationToOne)key).getRelatedKey();
//          return value instanceof ExtendedOrmUjo
//                  ? ((ExtendedOrmUjo)value).readFK(key)
//                  : new ForeignKey(key.of((Ujo)value));
//      }
// Effectiva: toto se volá cyklicky a navíc se předává špatná key (z původního objektu místo z cizího), pak se vrací nesmysly
//        if (value instanceof ExtendedOrmUjo) {
//            return ((ExtendedOrmUjo) value).readFK(key);
//        }
        if (session!=null) {
            final OrmUjo ujo = value instanceof OrmUjo
                    ? (OrmUjo) value
                    : this ;
            return session.readFK(ujo, key);
        }
        throw new NullPointerException("Can't get FK form the key '"+key+"' due the missing Session");
    }

    // ===== STATIC METHODS: Key Facotory =====

    /** Create a factory with a cammel-case Key name generator.
     * <br>Note: after declarations of all properties is recommend to call method {@code KeyFactory.close()};
     */
    protected static <UJO extends Ujo, FACTORY extends KeyFactory<UJO>> FACTORY newCamelFactory(Class<? extends UJO> ujoClass) {
        return (FACTORY) new OrmKeyFactory(ujoClass, true);
    }

    /** Create a base factory Key name generator where key name is the same as its field name.
     * <br>Note: after declarations of all properties is recommend to call method {@code KeyFactory.close()};<commit_msg>ORM: An exception message is fixed in the lazy-loading<commit_after>/*
 *  Copyright 2009-2014 <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 org.ujorm.implementation.orm;

import java.util.HashSet;
import java.util.Set;
import org.ujorm.Key;
import org.ujorm.Ujo;
import org.ujorm.UjoAction;
import org.ujorm.core.KeyFactory;
import org.ujorm.core.UjoManager;
import org.ujorm.core.annot.PackagePrivate;
import org.ujorm.extensions.Property;
import org.ujorm.implementation.quick.QuickUjo;
import org.ujorm.orm.ExtendedOrmUjo;
import org.ujorm.orm.ForeignKey;
import org.ujorm.orm.OrmKeyFactory;
import org.ujorm.orm.OrmUjo;
import org.ujorm.orm.Session;
import static org.ujorm.extensions.Property.UNDEFINED_INDEX;

/**
 * This abstract implementation of the OrmUjo interface is situable
 * for implementation the persistent entities.
 * <br>Instances of the OrmTable can be serializable, but you need to know that only
 * business data will be transferred, the session and property changes will not be passed.
 * <br>The sample of use:
 * <pre class="pre">
 *  <span class="comment">&#47;** Using INSERT *&#47;</span>
 *  <span class="keyword-directive">public</span> <span class="keyword-directive">void</span> useCreateItem() {
 *
 *      Order order = <span class="keyword-directive">new</span> Order();
 *      order.set(Order.DATE, <span class="keyword-directive">new</span> Date());
 *      order.set(Order.DESCR, <span class="character">&quot;John's order&quot;</span>);
 *
 *      Item item = <span class="keyword-directive">new</span> Item();
 *      item.set(Item.ORDER, order);
 *      item.set(Item.DESCR, <span class="character">&quot;Yellow table&quot;</span>);
 *
 *      Session session = OrmHandler.getInstance().getSession();
 *      session.save(order);
 *      session.save(item);
 *      session.commit();
 *  }</pre>
 *
 * @author <NAME>
 * @see org.ujorm.implementation.orm.RelationToMany
 * @see org.ujorm.core.UjoIterator
 */
public abstract class OrmTable<U extends OrmTable> extends QuickUjo implements ExtendedOrmUjo<U> {

    /** An empty array of the UJO keys */
    @PackagePrivate
    static final Key[] EMPTY = new Key[0];
    /** ORM session */
    transient private Session session;
    /** Set of changes */
    transient private Set<Key> changes = null;

    public OrmTable() {
    }

    /** Read a session */
    @Override
    public Session readSession() {
        return session;
    }

    /** Write a session */
    @Override
    public void writeSession(Session session) {
        this.session = session;
    }

    /** A method for an internal use only. */
    @Override
    public void writeValue(Key<?,?> key, Object value) {
        if (session!=null) {
            if (changes==null) {
                changes = new HashSet<Key>(8);
            }
            changes.add(key);
        }
        super.writeValue(key, value);
    }

    /**
     * Returns keys of changed values in a time when any <strong>session</strong> is assigned.
     * The method is used by a SQL UPDATE statement to update assigned values only.
     * Implementation tip: create a new property type of {@link Set<Key>}
     * and in the method writeValue assign the current Key always.
     * @param clear True value clears all the key changes.
     * @return Key array of the modified values.
     */
    @Override
    public Key[] readChangedProperties(boolean clear) {
        final Key[] result
            = changes==null || changes.isEmpty()
            ? EMPTY
            : changes.toArray(new Key[changes.size()])
            ;
        if (clear) {
            changes = null;
        }
        return result;
    }

    /** Getter based on Key implemeted by a pattern UjoExt */
    @SuppressWarnings("unchecked")
    public final <UJO extends U, VALUE> VALUE get(final Key<UJO, VALUE> key) {
        final VALUE result = key.of((UJO)this);
        return result;
    }

    /** Setter  based on Key. Type of value is checked in the runtime.
     * The method was implemented by a pattern UjoExt.
     */
    @SuppressWarnings({"unchecked"})
    public final <UJO extends U, VALUE> U set
        ( final Key<UJO, VALUE> key
        , final VALUE value
        ) {
        UjoManager.assertAssign(key, value);
        key.setValue((UJO)this, value);
        return (U) this;
    }

    /** Test an authorization of the action. */
    @Override
    public boolean readAuthorization(UjoAction action, Key key, Object value) {
        switch (action.getType()) {
            case UjoAction.ACTION_TO_STRING:
                return !(key instanceof RelationToMany);
            default:
                return super.readAuthorization(action, key, value);
        }
    }

    /** Read the foreign key.
     * This is useful to obtain the foreign key value without (lazy) loading the entire object.
     * If the lazy object is loaded, the method will need the Session to build the ForeignKey instance.
     * <br>NOTE: The method is designed for developers only, the Ujorm doesn't call it newer.
     * @param key Must be direct key only ({@link Key#isDirect()}==true)
     * @return If no related object is available, then the result has the NULL value.
     * @throws IllegalStateException Method throws an exception for a wrong key type.
     * @throws NullPointerException Method throws an exception if a Session is missing after a lazy initialization of the key.
     */
    @Override
    public <UJO extends U> ForeignKey readFK(Key<UJO, ? extends OrmUjo> key) throws IllegalStateException {
        final Object value = super.readValue(key);
        if (value==null || value instanceof ForeignKey) {
            return (ForeignKey) value;
        }
//      if (key instanceof RelationToOne) {
//          // TODO: fix the case the key is a relation:
//          final Key key = ((RelationToOne)key).getRelatedKey();
//          return value instanceof ExtendedOrmUjo
//                  ? ((ExtendedOrmUjo)value).readFK(key)
//                  : new ForeignKey(key.of((Ujo)value));
//      }
// Effectiva: toto se volá cyklicky a navíc se předává špatná key (z původního objektu místo z cizího), pak se vrací nesmysly
//      if (value instanceof ExtendedOrmUjo) {
//          return ((ExtendedOrmUjo) value).readFK(key);
//      }
        if (session!=null) {
            final OrmUjo ujo = value instanceof OrmUjo
                    ? (OrmUjo) value
                    : this ;
            return session.readFK(ujo, key);
        }
        throw new NullPointerException("Can't get FK by the key '"
                + key.getFullName()
                + "' due a missing Session");
    }

    // ===== STATIC METHODS: Key Facotory =====

    /** Create a factory with a cammel-case Key name generator.
     * <br>Note: after declarations of all properties is recommend to call method {@code KeyFactory.close()};
     */
    protected static <UJO extends Ujo, FACTORY extends KeyFactory<UJO>> FACTORY newCamelFactory(Class<? extends UJO> ujoClass) {
        return (FACTORY) new OrmKeyFactory(ujoClass, true);
    }

    /** Create a base factory Key name generator where key name is the same as its field name.
     * <br>Note: after declarations of all properties is recommend to call method {@code KeyFactory.close()};
{'fraction_non_alphanumeric': 0.10634460547504025, 'fraction_numerical': 0.002318840579710145, 'mean_word_length': 2.718802395209581, 'pattern_counts': {'":': 0, '<': 87, '<?xml version=': 0, '>': 87, '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': '7592262', 'n_tokens_mistral': 4487, 'n_tokens_neox': 4392, 'n_words': 1515}