<commit_before>
/*
 * Copyright (C) 2011 <NAME>. All rights reserved.
 *
 * $Id$
 */

package org.dellroad.stuff.schema;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.dellroad.stuff.graph.TopologicalSorter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Handles the initialization and schema maintenance of a database.
 *
 * <p>
 * In this class, a <b>database</b> is some stateful object whose structure and/or content may need to change over time.
 * <b>Updates</b> are uniquely named objects capable of making such changes. Databases are also capable of storing the
 * names of the already-applied updates.
 *
 * <p>
 * Given a database and a set of current updates, this class will ensure that a database is initialized if necessary
 * and up-to-date with respect to the updates.
 *
 * <p>
 * The primary method is {@link #initializeAndUpdateDatabase initializeAndUpdateDatabase()}, which will:
 * <ul>
 * <li>Initialize an {@linkplain #databaseNeedsInitialization empty} database (if necessary);</li>
 * <li>Apply any outstanding {@link SchemaUpdate}s as needed, ordered properly according to
 *      their {@linkplain SchemaUpdate#getRequiredPredecessors predecessor constraints}; and</li>
 * <li>Keep track of which {@link SchemaUpdate}s have already been applied across restarts.</li>
 * </ul>
 * </p>
 *
 * @param <D> database type
 * @param <T> database transaction type
 */
public abstract class AbstractSchemaUpdater<D, T> {

    protected final Logger log = LoggerFactory.getLogger(this.getClass());

    private Collection<? extends SchemaUpdate<T>> updates;
    private boolean ignoreUnrecognizedUpdates;

    /**
     * Get the configured updates. This property is required.
     *
     * @return configured updates
     * @see #setUpdates setUpdates()
     */
    public Collection<? extends SchemaUpdate<T>> getUpdates() {
        return this.updates;
    }

    /**
     * Configure the updates.
     * This should be the set of all updates that may need to be applied to the database.
     *
     * <p>
     * For any given application, ideally this set should be "write only" in the sense that once an update is added to the set
     * and applied to one or more actual databases, the update and its name should thereafter never change. Otherwise,
     * it would be possible for different databases to have inconsistent schemas even though the same updates were recorded.
     *
     * <p>
     * Furthermore, if not configured to {@linkplain #setIgnoreUnrecognizedUpdates ignore unrecognized updates already applied}
     * (the default behavior), then updates must never be removed from this set as the application evolves;
     * see {@link #setIgnoreUnrecognizedUpdates} for more information on the rationale.
     *
     * @param updates all updates; each update must have a unique {@link SchemaUpdate#getName name}.
     * @see #getUpdates
     * @see #setIgnoreUnrecognizedUpdates
     */
    public void setUpdates(Collection<? extends SchemaUpdate<T>> updates) {
        this.updates = updates;
    }

    /**
     * Determine whether unrecognized updates are ignored or cause an exception.
     *
     * @return true if unrecognized updates should be ignored, false if they should cause an exception to be thrown
     * @see #setIgnoreUnrecognizedUpdates setIgnoreUnrecognizedUpdates()
     */
    public boolean isIgnoreUnrecognizedUpdates() {
        return this.ignoreUnrecognizedUpdates;
    }

    /**
     * Configure behavior when an unknown update is registered as having already been applied in the database.
     *
     * <p>
     * The default behavior is <code>false</code>, which results in an exception being thrown. This protects against
     * accidental downgrades (i.e., running older code against a database with a newer schema), which are not supported.
     * However, this also requires that all updates that might ever possibly have been applied to the database be
     * present in the set of configured updates.
     *
     * <p>
     * Setting this to <code>true</code> will result in unrecognized updates simply being ignored.
     * This setting loses the downgrade protection but allows obsolete schema updates to be dropped over time.
     *
     * @param ignoreUnrecognizedUpdates whether to ignore unrecognized updates
     * @see #isIgnoreUnrecognizedUpdates
     */
    public void setIgnoreUnrecognizedUpdates(boolean ignoreUnrecognizedUpdates) {
        this.ignoreUnrecognizedUpdates = ignoreUnrecognizedUpdates;
    }

    /**
     * Perform database schema initialization and updates.
     *
     * <p>
     * This method applies the following logic: if the {@linkplain #databaseNeedsInitialization database needs initialization},
     * then {@linkplain #initializeDatabase initialize the database} and {@linkplain #recordUpdateApplied record} each update
     * as having been applied; then, apply any unapplied updates as needed.
     *
     * <p>
     * Note this implies the database initialization must initialize the database to its current, up-to-date state
     * (with respect to the set of updates), not its original, pre-update state.
     *
     * <p>
     * The database initialization step, and each of the update steps, is performed within its own transaction.
     *
     * @param database the database to initialize (if necessary) and update
     * @throws Exception if an update fails
     * @throws IllegalStateException if this instance is not configured to {@linkplain #setIgnoreUnrecognizedUpdates ignore
     * unrecognized updates} and an unrecognized update is registered in the update table as having already been applied
     * @throws IllegalArgumentException if two configured updates have the same name
     * @throws IllegalArgumentException if any configured update has a required predecessor which is not also a configured update
     *  (i.e., if the updates are not transitively closed under predecessors)
     */
    public synchronized void initializeAndUpdateDatabase(D database) throws Exception {

        // Log
        this.log.info("verifying database");

        // First, initialize if necessary
        this.applyInTransaction(database, new DatabaseAction<T>() {
            @Override
            public void apply(T transaction) throws Exception {
<commit_msg>Javaoc tweaks.<commit_after>
/*
 * Copyright (C) 2011 <NAME>. All rights reserved.
 *
 * $Id$
 */

package org.dellroad.stuff.schema;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.dellroad.stuff.graph.TopologicalSorter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Handles the initialization and schema maintenance of a database.
 *
 * <p>
 * In this class, a <b>database</b> is some stateful object whose structure and/or content may need to change over time.
 * <b>Updates</b> are uniquely named objects capable of making such changes. Databases are also capable of storing the
 * names of the already-applied updates.
 *
 * <p>
 * Given a database and a set of current updates, this class will ensure that a database is initialized if necessary
 * and up-to-date with respect to the updates.
 *
 * <p>
 * The primary method is {@link #initializeAndUpdateDatabase initializeAndUpdateDatabase()}, which will:
 * <ul>
 * <li>Initialize an {@linkplain #databaseNeedsInitialization empty} database (if necessary);</li>
 * <li>Apply any outstanding {@link SchemaUpdate}s as needed, ordered properly according to
 *      their {@linkplain SchemaUpdate#getRequiredPredecessors predecessor constraints}; and</li>
 * <li>Keep track of which {@link SchemaUpdate}s have already been applied across restarts.</li>
 * </ul>
 * </p>
 *
 * @param <D> database type
 * @param <T> database transaction type
 */
public abstract class AbstractSchemaUpdater<D, T> {

    protected final Logger log = LoggerFactory.getLogger(this.getClass());

    private Collection<? extends SchemaUpdate<T>> updates;
    private boolean ignoreUnrecognizedUpdates;

    /**
     * Get the configured updates. This property is required.
     *
     * @return configured updates
     * @see #setUpdates setUpdates()
     */
    public Collection<? extends SchemaUpdate<T>> getUpdates() {
        return this.updates;
    }

    /**
     * Configure the updates.
     * This should be the set of all updates that may need to be applied to the database.
     *
     * <p>
     * For any given application, ideally this set should be "write only" in the sense that once an update is added to the set
     * and applied to one or more actual databases, the update and its name should thereafter never change. Otherwise,
     * it would be possible for different databases to have inconsistent schemas even though the same updates were recorded.
     *
     * <p>
     * Furthermore, if not configured to {@linkplain #setIgnoreUnrecognizedUpdates ignore unrecognized updates already applied}
     * (the default behavior), then updates must never be removed from this set as the application evolves;
     * see {@link #setIgnoreUnrecognizedUpdates} for more information on the rationale.
     *
     * @param updates all updates; each update must have a unique {@link SchemaUpdate#getName name}.
     * @see #getUpdates
     * @see #setIgnoreUnrecognizedUpdates
     */
    public void setUpdates(Collection<? extends SchemaUpdate<T>> updates) {
        this.updates = updates;
    }

    /**
     * Determine whether unrecognized updates are ignored or cause an exception.
     *
     * @return true if unrecognized updates should be ignored, false if they should cause an exception to be thrown
     * @see #setIgnoreUnrecognizedUpdates setIgnoreUnrecognizedUpdates()
     */
    public boolean isIgnoreUnrecognizedUpdates() {
        return this.ignoreUnrecognizedUpdates;
    }

    /**
     * Configure behavior when an unknown update is registered as having already been applied in the database.
     *
     * <p>
     * The default behavior is <code>false</code>, which results in an exception being thrown. This protects against
     * accidental downgrades (i.e., running older code against a database with a newer schema), which are not supported.
     * However, this also requires that all updates that might ever possibly have been applied to the database be
     * present in the set of configured updates.
     *
     * <p>
     * Setting this to <code>true</code> will result in unrecognized updates simply being ignored.
     * This setting loses the downgrade protection but allows obsolete schema updates to be dropped over time.
     *
     * @param ignoreUnrecognizedUpdates whether to ignore unrecognized updates
     * @see #isIgnoreUnrecognizedUpdates
     */
    public void setIgnoreUnrecognizedUpdates(boolean ignoreUnrecognizedUpdates) {
        this.ignoreUnrecognizedUpdates = ignoreUnrecognizedUpdates;
    }

    /**
     * Perform database schema initialization and updates.
     *
     * <p>
     * This method applies the following logic: if the {@linkplain #databaseNeedsInitialization database needs initialization},
     * then {@linkplain #initializeDatabase initialize the database} and {@linkplain #recordUpdateApplied record} each update
     * as having been applied; otherwise, {@linkplain #apply apply} any {@linkplain #getAppliedUpdateNames unapplied updates}
     * as needed.
     *
     * <p>
     * Note this implies the database initialization must initialize the database to its current, up-to-date state
     * (with respect to the set of all available updates), not its original, pre-update state.
     *
     * <p>
     * The database initialization step, and each of the update steps, is {@linkplain #applyInTransaction performed within
     * its own transaction}.
     *
     * @param database the database to initialize (if necessary) and update
     * @throws Exception if an update fails
     * @throws IllegalStateException if this instance is not configured to {@linkplain #setIgnoreUnrecognizedUpdates ignore
     * unrecognized updates} and an unrecognized update has already been applied
     * @throws IllegalArgumentException if two configured updates have the same name
     * @throws IllegalArgumentException if any configured update has a required predecessor which is not also a configured update
     *  (i.e., if the updates are not transitively closed under predecessors)
     */
    public synchronized void initializeAndUpdateDatabase(D database) throws Exception {

        // Log
        this.log.info("verifying database");

        // First, initialize if necessary
        this.applyInTransaction(database, new DatabaseAction<T>() {
            @Override
            public void apply(T transaction) throws Exception {

{'fraction_non_alphanumeric': 0.07367305216930416, 'fraction_numerical': 0.000908609070947225, 'mean_word_length': 3.520191649555099, 'pattern_counts': {'":': 0, '<': 79, '<?xml version=': 0, '>': 79, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, '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': '4625830', 'n_tokens_mistral': 3346, 'n_tokens_neox': 3238, 'n_words': 1416}