package liquibase.sqlgenerator;

import liquibase.Scope;
import liquibase.change.Change;
import liquibase.database.Database;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.exception.ValidationErrors;
import liquibase.exception.Warnings;
import liquibase.servicelocator.ServiceLocator;
import liquibase.sql.Sql;
import liquibase.statement.SqlStatement;
import liquibase.structure.DatabaseObject;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.*;

/**
 * SqlGeneratorFactory is a singleton registry of SqlGenerators.
 * Use the register(SqlGenerator) method to add custom SqlGenerators,
 * and the getBestGenerator() method to retrieve the SqlGenerator that should be used for a given SqlStatement.
 */
public class SqlGeneratorFactory {

    private static SqlGeneratorFactory instance;
    //caches for expensive reflection based calls that slow down Liquibase initialization: CORE-1207
    private final Map<Class<?>, Type[]> genericInterfacesCache = new HashMap<>();
    private final Map<Class<?>, Type> genericSuperClassCache = new HashMap<>();
    private List<SqlGenerator> generators = new ArrayList<>();
    private Map<String, SortedSet<SqlGenerator>> generatorsByKey = new HashMap<>();

    private SqlGeneratorFactory() {
        try {
            for (SqlGenerator generator : Scope.getCurrentScope().getServiceLocator().findInstances(SqlGenerator.class)) {
                register(generator);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Return singleton SqlGeneratorFactory
     */
    public static synchronized SqlGeneratorFactory getInstance() {
        if (instance == null) {
            instance = new SqlGeneratorFactory();
        }
        return instance;
    }

    public static synchronized void reset() {
        instance = new SqlGeneratorFactory();
    }

    public void register(SqlGenerator generator) {
        generators.add(generator);
    }

    public void unregister(SqlGenerator generator) {
        generators.remove(generator);
    }

    public void unregister(Class generatorClass) {
        SqlGenerator toRemove = null;
        for (SqlGenerator existingGenerator : generators) {
            if (existingGenerator.getClass().equals(generatorClass)) {
                toRemove = existingGenerator;
            }
        }
        unregister(toRemove);
    }

    protected Collection<SqlGenerator> getGenerators() {
        return generators;
    }

    public SortedSet<SqlGenerator> getGenerators(SqlStatement statement, Database database) {
        String databaseName = null;
        if (database == null) {
            databaseName = "NULL";
        } else {
            databaseName = database.getShortName();
        }

        int version;
        if (database == null) {
            version = 0;
        } else {
            try {
                version = database.getDatabaseMajorVersion();
            } catch (Exception e) {
                version = 0;
            }
        }

        String key = statement.getClass().getName()+":"+ databaseName+":"+ version;

        if (generatorsByKey.containsKey(key) && !generatorsByKey.get(key).isEmpty()) {
            SortedSet<SqlGenerator> result = new TreeSet<>(new SqlGeneratorComparator());
            result.addAll(generatorsByKey.get(key));
            result.retainAll(getGenerators());
            return result;
        }

        SortedSet<SqlGenerator> validGenerators = new TreeSet<>(new SqlGeneratorComparator());

        for (SqlGenerator generator : getGenerators()) {
            Class clazz = generator.getClass();
            Type classType = null;
            while (clazz != null) {
                if (classType instanceof ParameterizedType) {
                    checkType(classType, statement, generator, database, validGenerators);
                }

                for (Type type : getGenericInterfaces(clazz)) {
                    if (type instanceof ParameterizedType) {
                        checkType(type, statement, generator, database, validGenerators);
                    } else if (isTypeEqual(type, SqlGenerator.class)) {
                        //noinspection unchecked
                        if (generator.supports(statement, database)) {
                            validGenerators.add(generator);
                        }
                    }
                }
                classType = getGenericSuperclass(clazz);
                clazz = clazz.getSuperclass();
            }
        }
        generatorsByKey.put(key, validGenerators);
        return validGenerators;
    }

    private Type[] getGenericInterfaces(Class<?> clazz) {
        if(genericInterfacesCache.containsKey(clazz)) {
            return genericInterfacesCache.get(clazz);
        }
        Type[] genericInterfaces = clazz.getGenericInterfaces();
        genericInterfacesCache.put(clazz, genericInterfaces);
        return genericInterfaces;
    }

    private Type getGenericSuperclass(Class<?> clazz) {
        if(genericSuperClassCache.containsKey(clazz)) {
            return genericSuperClassCache.get(clazz);
        }
        Type genericSuperclass = clazz.getGenericSuperclass();
        genericSuperClassCache.put(clazz, genericSuperclass);
        return genericSuperclass;
    }

    private boolean isTypeEqual(Type aType, Class aClass) {
        if (aType instanceof Class) {
            return ((Class) aType).getName().equals(aClass.getName());
        }
        return aType.equals(aClass);
    }

    private void checkType(Type type, SqlStatement statement, SqlGenerator generator, Database database, SortedSet<SqlGenerator> validGenerators) {
        for (Type typeClass : ((ParameterizedType) type).getActualTypeArguments()) {
            if (typeClass instanceof TypeVariable) {
                typeClass = ((TypeVariable) typeClass).getBounds()[0];
            }

            if (isTypeEqual(typeClass, SqlStatement.class)) {
                return;
            }

            if (((Class) typeClass).isAssignableFrom(statement.getClass())) {
                if (generator.supports(statement, database)) {
                    validGenerators.add(generator);
                }
            }
        }
    }

    private SqlGeneratorChain createGeneratorChain(SqlStatement statement, Database database) {
        SortedSet<SqlGenerator> sqlGenerators = getGenerators(statement, database);
        if ((sqlGenerators == null) || sqlGenerators.isEmpty()) {
            return null;
        }
        //noinspection unchecked
        return new SqlGeneratorChain(sqlGenerators);
    }

    public Sql[] generateSql(Change change, Database database) {
        SqlStatement[] sqlStatements = change.generateStatements(database);
        if (sqlStatements == null) {
            return new Sql[0];
        } else {
            return generateSql(sqlStatements, database);
        }
    }

    public Sql[] generateSql(SqlStatement[] statements, Database database) {
        List<Sql> returnList = new ArrayList<>();
        SqlGeneratorFactory factory = SqlGeneratorFactory.getInstance();
        for (SqlStatement statement : statements) {
            Sql[] sqlArray = factory.generateSql(statement, database);
            if ((sqlArray != null) && (sqlArray.length > 0)) {
              List<Sql> sqlList = Arrays.asList(sqlArray);
              returnList.addAll(sqlList);
            }
        }
        return returnList.toArray(new Sql[returnList.size()]);
    }

    public Sql[] generateSql(SqlStatement statement, Database database) {
        SqlGeneratorChain generatorChain = createGeneratorChain(statement, database);
        if (generatorChain == null) {
            throw new IllegalStateException("Cannot find generators for database " + database.getClass() + ", statement: " + statement);
        }
        return generatorChain.generateSql(statement, database);
    }

    /**
     * Return true if the SqlStatement class queries the database in any way to determine Statements to execute.
     * If the statement queries the database, it cannot be used in updateSql type operations
     */
    public boolean generateStatementsVolatile(SqlStatement statement, Database database) {
        for (SqlGenerator generator : getGenerators(statement, database)) {
            if (generator.generateStatementsIsVolatile(database)) {
                return true;
            }
        }
        return false;
    }

    public boolean generateRollbackStatementsVolatile(SqlStatement statement, Database database) {
        for (SqlGenerator generator : getGenerators(statement, database)) {
            if (generator.generateRollbackStatementsIsVolatile(database)) {
                return true;
            }
        }
        return false;
    }

    public boolean supports(SqlStatement statement, Database database) {
        return !getGenerators(statement, database).isEmpty();
    }

    public ValidationErrors validate(SqlStatement statement, Database database) {
        //noinspection unchecked
        SqlGeneratorChain generatorChain = createGeneratorChain(statement, database);
        if (generatorChain == null) {
            throw new UnexpectedLiquibaseException("Unable to create generator chain for "+statement.getClass().getName()+" on "+database.getShortName());
        }
        return generatorChain.validate(statement, database);
    }

    public Warnings warn(SqlStatement statement, Database database) {
        //noinspection unchecked
        return createGeneratorChain(statement, database).warn(statement, database);
    }

    public Set<DatabaseObject> getAffectedDatabaseObjects(SqlStatement statement, Database database) {
        Set<DatabaseObject> affectedObjects = new HashSet<>();

        SqlGeneratorChain sqlGeneratorChain = createGeneratorChain(statement, database);
        if (sqlGeneratorChain != null) {
            //noinspection unchecked
            Sql[] sqls = sqlGeneratorChain.generateSql(statement, database);
            if (sqls != null) {
                for (Sql sql : sqls) {
                    affectedObjects.addAll(sql.getAffectedDatabaseObjects());
                }
            }
        }
        return affectedObjects;
    }
}


// Version:   $Id: $
// Copyright: Copyright(c) 2007 Trace Financial Limited
package org.liquibase.maven.plugins;

import liquibase.exception.LiquibaseException;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;

import java.io.File;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.*;

/**
 * A Utilities class for Maven plugins.
 * @author Peter Murray
 */
public class MavenUtils {

  public static final String LOG_SEPARATOR =
          "------------------------------------------------------------------------";

  /**
   * Obtains a {@link ClassLoader} that can load from the Maven project dependencies. If
   * the dependencies have not be resolved (or there are none) then this will just end up
   * delegating to the parent {@link ClassLoader} of this class.
   * @return The ClassLoader that can load the resolved dependencies for the Maven
   *         project.
   * @throws java.net.MalformedURLException If any of the dependencies cannot be resolved
   * into a URL.
   */
  public static ClassLoader getArtifactClassloader(MavenProject project,
                                                   boolean includeArtifact,
                                                   boolean includeTestOutputDirectory,
                                                   Class clazz,
                                                   Log log,
                                                   boolean verbose)
          throws MalformedURLException {
    if (verbose) {
      log.info("Loading artifacts into URLClassLoader");
    }
    Set<URI> uris = new HashSet<>();
    // Find project dependencies, including the transitive ones.
    Set dependencies = project.getArtifacts();
    if ((dependencies != null) && !dependencies.isEmpty()) {
        for (Iterator it = dependencies.iterator(); it.hasNext();) {
            addArtifact(uris, (Artifact) it.next(), log, verbose);
        }
    } else {
      log.info("there are no resolved artifacts for the Maven project.");
    }

    // Include the artifact for the actual maven project if requested
    if (includeArtifact) {
      // If the actual artifact can be resolved, then use that, otherwise use the build
      // directory as that should contain the files for this project that we will need to
      // run against. It is possible that the build directly could be empty, but we cannot
      // directly include the source and resources as the resources may require filtering
      // to replace any placeholders in the resource files.
      Artifact a = project.getArtifact();
      if (a.getFile() != null) {
        addArtifact(uris, a, log, verbose);
      } else {
        addFile(uris, new File(project.getBuild().getOutputDirectory()), log, verbose);
      }
    }
     if (includeTestOutputDirectory) {
        addFile(uris, new File(project.getBuild().getTestOutputDirectory()), log, verbose);
    }
    if (verbose) {
      log.info(LOG_SEPARATOR);
    }

    List<URI> uriList = new ArrayList<>(uris);
    URL[] urlArray = new URL[uris.size()];
    for (int i=0; i<uris.size(); i++ ) {
        urlArray[i] = uriList.get(i).toURL();
    }
    return new URLClassLoader(urlArray, clazz.getClassLoader());
  }

  /**
   * Adds the artifact file into the set of URLs so it can be used in a URLClassLoader.
   * @param urls The set to add the artifact file URL to.
   * @param artifact The Artifact to resolve the file for.
   * @throws MalformedURLException If there is a problem creating the URL for the file.
   */
  private static void addArtifact(Set<URI> urls,
                                  Artifact artifact,
                                  Log log,
                                  boolean verbose)
          throws MalformedURLException {
    File f = artifact.getFile();
    if (f == null) {
      log.warn("Artifact with no actual file, '" + artifact.getGroupId()
               + ":" + artifact.getArtifactId() + "'");
    } else {
      addFile(urls, f, log, verbose);
    }
//    if (f != null) {
//      URL fileURL = f.toURI().toURL();
//      if (verbose) {
//        log.info("  artifact: " + fileURL);
//      }
//      urls.add(fileURL);
//    } else {
//      log.warning("Artifact with no actual file, '" + artifact.getGroupId()
//               + ":" + artifact.getArtifactId() + "'");
//    }
  }

  private static void addFile(Set<URI> urls, File f, Log log, boolean verbose)
          throws MalformedURLException {
    if (f != null) {
      URI fileUri = f.toURI();
      if (verbose) {
        log.info("  artifact: " + fileUri);
      }
      urls.add(fileUri);
    }
  }

  public static Connection getDatabaseConnection(ClassLoader classLoader,
                                                 String driver,
                                                 String url,
                                                 String username,
                                                 String password)
          throws LiquibaseException {
    Driver dbDriver = null;
    try {
      dbDriver = (Driver)Class.forName(driver,
                                       true,
                                       classLoader).getConstructor().newInstance();
    }
    catch (ClassNotFoundException e) {
      throw new LiquibaseException("Missing Class '" + e.getMessage() + "'. Database "
                                   + "driver may not be included in the project "
                                   + "dependencies or with wrong scope.");
    } catch (ReflectiveOperationException e) {
      throw new LiquibaseException("Failed to load JDBC driver, " + driver, e);
    }

    Properties info = new Properties();
    info.put("user", username);
    info.put("password", password);
    try {
      Connection connection = dbDriver.connect(url, info);
      if (connection == null) {
        throw new LiquibaseException("Connection could not be created to " + url
                                     + " with driver " + dbDriver.getClass().getName()
                                     + ".  Possibly the wrong driver for the given "
                                     + "database URL");
      }
      return connection;
    }
    catch (SQLException e) {
      throw new LiquibaseException(e);
    }
  }

  /**
   * Recursively searches for the field specified by the fieldName in the class and all
   * the super classes until it either finds it, or runs out of parents.
   * @param clazz The Class to start searching from.
   * @param fieldName The name of the field to retrieve.
   * @return The {@link Field} identified by the field name.
   * @throws NoSuchFieldException If the field was not found in the class or any of its
   * super classes.
   */
  public static Field getDeclaredField(Class clazz, String fieldName)
          throws NoSuchFieldException {
    Field f = getField(clazz, fieldName);
    if (f == null) {
      // Try the parent class
      Class parent = clazz.getSuperclass();
      if (parent != null) {
        return getDeclaredField(parent, fieldName);
      } else {
        throw new NoSuchFieldException("The field '" + fieldName + "' could not be "
                                       + "found in the class of any of its parent "
                                       + "classes.");
      }
    } else {
      return f;
    }
  }

  private static Field getField(Class clazz, String fieldName) {
    Field[] fields = clazz.getDeclaredFields();
    for (int i = 0; i < fields.length; i++) {
      if (fields[i].getName().equals(fieldName)) {
        return fields[i];
      }
    }
    return null;
  }
}


package liquibase.integration.commandline;

import liquibase.resource.ClassLoaderResourceAccessor;
import liquibase.resource.InputStreamList;

import java.io.IOException;
import java.util.*;

/**
 * Extension of {@link liquibase.resource.ClassLoaderResourceAccessor} that adds extra fuzzy searching logic based on
 * what users may enter that is different than what is exactly correct.
 */
public class CommandLineResourceAccessor extends ClassLoaderResourceAccessor {

    public CommandLineResourceAccessor(ClassLoader loader) {
        super(loader);
    }

    @Override
    public InputStreamList openStreams(String relativeTo, String streamPath) throws IOException {
        InputStreamList resourcesAsStream = super.openStreams(relativeTo, streamPath);
        if (resourcesAsStream == null) {
            for (String altPath : getAlternatePaths(streamPath)) {
                resourcesAsStream = super.openStreams(relativeTo, altPath);
                if (resourcesAsStream != null) {
                    return resourcesAsStream;
                }
            }
        }
        return resourcesAsStream;
    }

    @Override
    public SortedSet<String> list(String relativeTo, String path, boolean includeFiles, boolean includeDirectories, boolean recursive) throws IOException {
        SortedSet<String> contents = new TreeSet<>();
        Set<String> superList = super.list(relativeTo, path, includeFiles, includeDirectories, recursive);
        if (superList != null) {
            contents.addAll(superList);
        }
        for (String altPath : getAlternatePaths(path)) {
            contents.addAll(super.list(relativeTo, altPath, includeFiles, includeDirectories, recursive));
        }
        if (contents.isEmpty()) {
            return null;
        }
        return contents;
    }

    /**
     * Return alternate options for the given path that the user maybe meant. Return in order of likelihood.
     */
    protected List<String> getAlternatePaths(String path) {
        List<String> alternatePaths = new ArrayList<>();

        if (path.startsWith("/")) { //People are often confused about leading slashes in resource paths...
            alternatePaths.add(path.substring(1));
        }

        return alternatePaths;

    }

}


package liquibase;

import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.util.StringUtil;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Encapsulates logic for evaluating if a set of runtime contexts matches a context expression string.
 */
public class ContextExpression {

    private HashSet<String> contexts = new HashSet<>();
    private String originalString;

    public ContextExpression() {
    }

    public ContextExpression(String... contexts) {
        if (contexts.length == 1) {
            parseContextString(contexts[0]);
        } else {
            for (String context : contexts) {
                parseContextString(context.toLowerCase());
            }
        }
    }

    public ContextExpression(String contexts) {
        parseContextString(contexts);
        this.originalString = contexts;
    }

    public ContextExpression(Collection<String> contexts) {
        if (contexts != null) {
            for (String context : contexts) {
                this.contexts.add(context.toLowerCase());
            }
        }
    }

    private void parseContextString(String contexts) {
        contexts = StringUtil.trimToNull(contexts);

        if (contexts == null) {
            return;
        }
        for (String context : StringUtil.splitAndTrim(contexts, ",")) {
            this.contexts.add(context.toLowerCase());
        }

    }

    public boolean add(String context) {
        return this.contexts.add(context.toLowerCase());
    }

    public Set<String> getContexts() {
        return Collections.unmodifiableSet(contexts);
    }

    @Override
    public String toString() {
        if (originalString != null) {
            return originalString;
        }
        return "(" + StringUtil.join(new TreeSet<String>(this.contexts), "), (") + ")";
    }

    /**
     * Returns true if the passed runtime contexts match this context expression
     */
    public boolean matches(Contexts runtimeContexts) {
        if ((runtimeContexts == null) || runtimeContexts.isEmpty()) {
            return true;
        }
        if (this.contexts.isEmpty()) {
            return true;
        }

        for (String expression : this.contexts) {
            if (matches(expression, runtimeContexts)) {
                return true;
            }
        }
        return false;
    }

    private boolean matches(String expression, Contexts runtimeContexts) {
        if (runtimeContexts.isEmpty()) {
            return true;
        }

        if (":TRUE".equals(expression.trim())) {
            return true;
        }
        if (":FALSE".equals(expression.trim())) {
            return false;
        }

        while (expression.contains("(")) {
            Pattern pattern = Pattern.compile("(.*?)\\((.*?)\\)(.*)");
            Matcher matcher = pattern.matcher(expression);
            if (!matcher.matches()) {
                throw new UnexpectedLiquibaseException("Cannot parse context pattern "+expression);
            }
            String parenExpression = matcher.group(2);

            parenExpression = ":"+String.valueOf(matches(parenExpression, runtimeContexts)).toUpperCase();

            expression = matcher.group(1)+" "+parenExpression+" "+matcher.group(3);
        }

        String[] orSplit = expression.split("\\s+or\\s+");
        if (orSplit.length > 1) {
            for (String split : orSplit) {
                if (matches(split, runtimeContexts)) {
                    return true;
                }
            }
            return false;
        }

        String[] andSplit = expression.split("\\s+and\\s+");
        if (andSplit.length > 1) {
            for (String split : andSplit) {
                if (!matches(split, runtimeContexts)) {
                    return false;
                }
            }
            return true;
        }

        boolean notExpression = false;
        if (expression.startsWith("!")) {
            notExpression = true;
            expression = expression.substring(1);
        }

        for (String context : runtimeContexts.getContexts()) {
            if (context.equalsIgnoreCase(expression)) {
                return !notExpression;
            }
        }
        return notExpression;

    }

    public boolean isEmpty() {
        return (this.contexts == null) || this.contexts.isEmpty();
    }

    public static boolean matchesAll(Collection<ContextExpression> expressions, Contexts contexts) {
        if ((expressions == null) || expressions.isEmpty()) {
            return true;
        }
        if ((contexts == null) || contexts.isEmpty()) {
            return true;
        }
        for (ContextExpression expression : expressions) {
            if (!expression.matches(contexts)) {
                return false;
            }
        }
        return true;
    }
}


package liquibase.integration.ant;

import liquibase.Liquibase;
import liquibase.Scope;
import liquibase.configuration.GlobalConfiguration;
import liquibase.configuration.LiquibaseConfiguration;
import liquibase.database.Database;
import liquibase.exception.DatabaseException;
import liquibase.exception.LiquibaseException;
import liquibase.integration.ant.type.ChangeLogParametersType;
import liquibase.integration.ant.type.DatabaseType;
import liquibase.logging.Logger;
import liquibase.resource.ClassLoaderResourceAccessor;
import liquibase.resource.CompositeResourceAccessor;
import liquibase.resource.FileSystemResourceAccessor;
import liquibase.resource.ResourceAccessor;
import liquibase.util.ui.UIFactory;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.Property;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;
import org.apache.tools.ant.types.resources.FileResource;

import java.io.*;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;

import static java.util.ResourceBundle.getBundle;

/**
 * Base class for all Ant Liquibase tasks.  This class sets up Liquibase and defines parameters
 * that are common to all tasks.
 */
public abstract class BaseLiquibaseTask extends Task {
    private static ResourceBundle coreBundle = getBundle("liquibase/i18n/liquibase-core");

    private AntClassLoader classLoader;
    private Liquibase liquibase;

    private Path classpath;
    private DatabaseType databaseType;
    private ChangeLogParametersType changeLogParameters;
    private boolean promptOnNonLocalDatabase;

    public BaseLiquibaseTask() {
        super();
    }

    @Override
    public void init() throws BuildException {
//        LogService.setLoggerFactory(new AntTaskLogService(this));
        classpath = new Path(getProject());
    }

    @Override
    public final void execute() throws BuildException {
        super.execute();
        log(coreBundle.getString("starting.liquibase"), Project.MSG_INFO);
        classLoader = getProject().createClassLoader(classpath);
        classLoader.setParent(this.getClass().getClassLoader());
        classLoader.setThreadContextLoader();
        validateParameters();
        Database database = null;
        try {
            ResourceAccessor resourceAccessor = createResourceAccessor(classLoader);
            database = createDatabaseFromType(databaseType);
            liquibase = new Liquibase(getChangeLogFile(), resourceAccessor, database);
            if(changeLogParameters != null) {
                changeLogParameters.applyParameters(liquibase);
            }
            if (isPromptOnNonLocalDatabase() && !liquibase.isSafeToRunUpdate() &&
                    UIFactory.getInstance().getFacade().promptForNonLocalDatabase(liquibase.getDatabase())) {
                log("User chose not to run task against a non-local database.", Project.MSG_INFO);
                return;
            }
            if(shouldRun()) {
                executeWithLiquibaseClassloader();
            }
        } catch (LiquibaseException e) {
            throw new BuildException("Unable to initialize Liquibase: " + e.getMessage(), e);
        } finally {
            closeDatabase(database);
            classLoader.resetThreadContextLoader();
            classLoader.cleanup();
            classLoader = null;
        }
    }

    protected abstract void executeWithLiquibaseClassloader() throws BuildException;

    protected Database createDatabaseFromConfiguredDatabaseType() {
        return createDatabaseFromType(databaseType);
    }

    protected Database createDatabaseFromType(DatabaseType databaseType) {
        return databaseType.createDatabase(classLoader);
    }

    protected Liquibase getLiquibase() {
        return liquibase;
    }

    /**
     * This method is designed to be overridden by subclasses when a change log is needed. By default it returns null.
     *
     * @return Returns null in this implementation. Subclasses that need a change log should implement.
     * @see AbstractChangeLogBasedTask#getChangeLogDirectory()
     */
    public String getChangeLogDirectory() {
      return null;
    }

    /**
     * This method is designed to be overridden by subclasses when a change log is needed. By default it returns null.
     *
     * @return Returns null in this implementation. Subclasses that need a change log should implement.
     * @see AbstractChangeLogBasedTask#getChangeLogFile()
     */
    protected String getChangeLogFile() {
        return null;
    }

    public void setChangeLogFile(String changeLogFile) {
        // This method is deprecated. Use child implementation.
    }

    protected boolean shouldRun() {
        LiquibaseConfiguration configuration = LiquibaseConfiguration.getInstance();
        GlobalConfiguration globalConfiguration = configuration.getConfiguration(GlobalConfiguration.class);
        if (!globalConfiguration.getShouldRun()) {
            log("Liquibase did not run because " + configuration.describeValueLookupLogic(globalConfiguration
                .getProperty(GlobalConfiguration.SHOULD_RUN)) + " was set to false", Project.MSG_INFO);
            return false;
        }
        return true;
    }

    protected String getDefaultOutputEncoding() {
        LiquibaseConfiguration liquibaseConfiguration = LiquibaseConfiguration.getInstance();
        GlobalConfiguration globalConfiguration = liquibaseConfiguration.getConfiguration(GlobalConfiguration.class);
        return globalConfiguration.getOutputEncoding();
    }

    /**
     * Subclasses that override this method must always call super.validateParameters() method.
     */
    protected void validateParameters() {
        if(databaseType == null) {
            throw new BuildException("A database or databaseref is required.");
        }
    }

    /**
     * Creates a suitable ResourceAccessor for use in an Ant task..
     *
     * @param classLoader The ClassLoader to use in the ResourceAccessor. It is preferable that it is an AntClassLoader.
     * @return A ResourceAccessor.
     */
    private ResourceAccessor createResourceAccessor(ClassLoader classLoader) {
        List<ResourceAccessor> resourceAccessors = new ArrayList<ResourceAccessor>();
        resourceAccessors.add(new FileSystemResourceAccessor(Paths.get(".").toAbsolutePath().getRoot().toFile()));
        resourceAccessors.add(new ClassLoaderResourceAccessor(classLoader));
        String changeLogDirectory = getChangeLogDirectory();
        if (changeLogDirectory != null) {
          changeLogDirectory = changeLogDirectory.trim().replace('\\', '/');  //convert to standard / if using absolute path on windows
          resourceAccessors.add(new FileSystemResourceAccessor(new File(changeLogDirectory)));
        }
        return new CompositeResourceAccessor(resourceAccessors.toArray(new ResourceAccessor[0]));
    }

    /*
     * Ant parameters
     */

    /**
     * Convenience method to safely close the database connection.
     *
     * @param database The database to close.
     */
    protected void closeDatabase(Database database) {
        try {
            if(database != null) {
                database.close();
            }
        } catch (DatabaseException e) {
            log("Error closing the database connection.", e, Project.MSG_WARN);
        }
    }

    public Path createClasspath() {
        if (this.classpath == null) {
            this.classpath = new Path(getProject());
        }
        return this.classpath.createPath();
    }

    public void setClasspathRef(Reference r) {
        createClasspath().setRefid(r);
    }

    public void addDatabase(DatabaseType databaseType) {
        if(this.databaseType != null) {
            throw new BuildException("Only one <database> element is allowed.");
        }
        this.databaseType = databaseType;
    }

    public void setDatabaseRef(Reference databaseRef) {
        databaseType = new DatabaseType(getProject());
        databaseType.setRefid(databaseRef);
    }

    public void addChangeLogParameters(ChangeLogParametersType changeLogParameters) {
        if(this.changeLogParameters != null) {
            throw new BuildException("Only one <changeLogParameters> element is allowed.");
        }
        this.changeLogParameters = changeLogParameters;
    }

    public void setChangeLogParametersRef(Reference changeLogParametersRef) {
        changeLogParameters = new ChangeLogParametersType(getProject());
        changeLogParameters.setRefid(changeLogParametersRef);
    }

    public boolean isPromptOnNonLocalDatabase() {
        return promptOnNonLocalDatabase;
    }

    /*************************
     * Deprecated parameters *
     *************************/

    public void setPromptOnNonLocalDatabase(boolean promptOnNonLocalDatabase) {
        this.promptOnNonLocalDatabase = promptOnNonLocalDatabase;
    }

    /**
     * Helper method for deprecated ant attributes. This method will be removed when the deprecated methods are removed.
     * Do not rely on this method.
     *
     * @return DatabaseType object. Created if doesn't exist.
     */
    private DatabaseType getDatabaseType() {
        if(databaseType == null) {
            databaseType = new DatabaseType(getProject());
        }
        return databaseType;
    }

    /**
     * Helper method for deprecated ant attributes. This method will be removed when the deprecated methods are removed.
     * Do not rely on this method.
     *
     * @return ChangeLogParametersType
     */
    private ChangeLogParametersType getChangeLogParametersType() {
        if(changeLogParameters == null) {
            changeLogParameters = new ChangeLogParametersType(getProject());
        }
        return changeLogParameters;
    }

    /**
     * @deprecated Use {@link DatabaseType#getDriver()} instead.
     */
    @Deprecated
    public String getDriver() {
        return getDatabaseType().getDriver();
    }

    /**
     * @deprecated Use {@link DatabaseType#setDriver(String)} instead.
     */
    @Deprecated
    public void setDriver(String driver) {
        log("The driver attribute is deprecated. Use a nested <database> element or set the databaseRef attribute instead.", Project.MSG_WARN);
        getDatabaseType().setDriver(driver);
    }

    /**
     * @deprecated Use {@link DatabaseType#getUrl()} instead.
     */
    @Deprecated
    public String getUrl() {
        return getDatabaseType().getUrl();
    }

    /**
     * @deprecated Use {@link DatabaseType#setUrl(String)} instead.
     */
    @Deprecated
    public void setUrl(String url) {
        log("The url attribute is deprecated. Use a nested <database> element or set the databaseRef attribute instead.", Project.MSG_WARN);
        getDatabaseType().setUrl(url);
    }

    /**
     * @deprecated Use {@link DatabaseType#getUser()} instead.
     */
    @Deprecated
    public String getUsername() {
        return getDatabaseType().getUser();
    }

    /**
     * @deprecated Use {@link DatabaseType#setUser(String)} instead.
     */
    @Deprecated
    public void setUsername(String username) {
        log("The username attribute is deprecated. Use a nested <database> element or set the databaseRef attribute instead.", Project.MSG_WARN);
        getDatabaseType().setUser(username);
    }

    /**
     * @deprecated Use {@link DatabaseType#getPassword()} instead.
     */
    @Deprecated
    public String getPassword() {
        return getDatabaseType().getPassword();
    }

    /**
     * @deprecated Use {@link DatabaseType#setPassword(String)} instead.
     */
    @Deprecated
    public void setPassword(String password) {
        log("The password attribute is deprecated. Use a nested <database> element or set the databaseRef attribute instead.", Project.MSG_WARN);
        getDatabaseType().setPassword(password);
    }

    /**
     * @deprecated Use {@link DatabaseType#getCurrentDateTimeFunction()} instead.
     */
    @Deprecated
    public String getCurrentDateTimeFunction() {
        return getDatabaseType().getCurrentDateTimeFunction();
    }

    /**
     * @deprecated Use {@link DatabaseType#setCurrentDateTimeFunction(String)} instead.
     */
    @Deprecated
    public void setCurrentDateTimeFunction(String currentDateTimeFunction) {
        log("The currentDateTimeFunction attribute is deprecated. Use a nested <database> element or set the databaseRef attribute instead.", Project.MSG_WARN);
        getDatabaseType().setCurrentDateTimeFunction(currentDateTimeFunction);
    }

    /**
     * This method does nothing. Use child implementations.
     */
    public FileResource getOutputFile() {
        // This method is deprecated. Use child implementations.
        return null;
    }

    /**
     * This method does nothing. Use child implementations.
     */
    public void setOutputFile(FileResource outputFile) {
        // This method is deprecated. Use child implementation.
    }

    /**
     * @deprecated Subclasses of this class should either instantiate their own output writers or use
     * {@link liquibase.integration.ant.AbstractChangeLogBasedTask} if their task involves a change log.
     */
    @Deprecated
    public Writer createOutputWriter() throws IOException {
        if (getOutputFile() == null) {
            return null;
        }
        GlobalConfiguration globalConfiguration = LiquibaseConfiguration.getInstance().getConfiguration(GlobalConfiguration.class);
        String encoding = globalConfiguration.getOutputEncoding();
        return new OutputStreamWriter(getOutputFile().getOutputStream(), encoding);
    }

    /**
     * @deprecated Subclasses of this class should either instantiate their own output writers or use
     * {@link liquibase.integration.ant.AbstractChangeLogBasedTask} if the task involves a change log.
     */
    @Deprecated
    public PrintStream createPrintStream() throws IOException {
        if (getOutputFile() == null) {
            return null;
        }
        GlobalConfiguration globalConfiguration = LiquibaseConfiguration.getInstance().getConfiguration(GlobalConfiguration.class);
        String encoding = globalConfiguration.getOutputEncoding();
        return new PrintStream(getOutputFile().getOutputStream(), false, encoding);
    }

    public void setOutputEncoding(String outputEncoding) {
        // This method is deprecated. Use child implementation.
    }

    /**
     * @deprecated Use {@link DatabaseType#getDefaultCatalogName()} instead.
     */
    @Deprecated
    public String getDefaultCatalogName() {
        return getDatabaseType().getDefaultCatalogName();
    }

    /**
     * @deprecated Use {@link DatabaseType#setDefaultCatalogName(String)} instead.
     */
    @Deprecated
    public void setDefaultCatalogName(String defaultCatalogName) {
        log("The defaultCatalogName attribute is deprecated. Use a nested <database> element or set the databaseRef attribute instead.", Project.MSG_WARN);
        getDatabaseType().setDefaultCatalogName(defaultCatalogName);
    }

    /**
     * @deprecated Use {@link DatabaseType#getDefaultSchemaName()} instead.
     */
    @Deprecated
    public String getDefaultSchemaName() {
        return getDatabaseType().getDefaultSchemaName();
    }

    /**
     * @deprecated Use {@link DatabaseType#setDefaultSchemaName(String)} instead.
     */
    @Deprecated
    public void setDefaultSchemaName(String defaultSchemaName) {
        log("The driver attribute is deprecated. Use a nested <database> element or set the databaseRef attribute instead.", Project.MSG_WARN);
        getDatabaseType().setDefaultSchemaName(defaultSchemaName);
    }

    /**
     * @deprecated Use {@link ChangeLogParametersType#addConfiguredChangeLogParameter(Property)} instead.
     */
    @Deprecated
    public void addConfiguredChangeLogProperty(ChangeLogProperty changeLogProperty) {
        log("The <changeLogProperty> element is deprecated. Use a nested <changeLogParameters> element instead.", Project.MSG_WARN);
        Property property = new Property();
        property.setName(changeLogProperty.getName());
        property.setValue(changeLogProperty.getValue());
        getChangeLogParametersType().addConfiguredChangeLogParameter(property);
    }

    /**
     * @deprecated The Liquibase class is now created automatically when the Ant task is executed. Use
     * {@link #getLiquibase()} instead.
     */
    @Deprecated
    protected Liquibase createLiquibase() throws Exception {
        return this.liquibase;
    }

    /**
     * @deprecated Use {@link #createDatabaseFromType(DatabaseType)} instead.
     */
    @Deprecated
    protected Database createDatabaseObject(String driverClassName,
                                            String databaseUrl,
                                            String username,
                                            String password,
                                            String defaultCatalogName,
                                            String defaultSchemaName,
                                            String databaseClass) throws Exception {
        return createDatabaseFromType(databaseType);
    }

    /**
     * This method no longer does anything. Please extend from
     * {@link liquibase.integration.ant.AbstractChangeLogBasedTask} which has the equivalent method.
     */
    public String getContexts() {
        return null;
    }

    /**
     * This method no longer does anything. Please extend from
     * {@link liquibase.integration.ant.AbstractChangeLogBasedTask} which has the equivalent method.
     */
    public void setContexts(String cntx) {
        // Parent method is deprecated. Use child implementations.
    }

    /**
     * @deprecated Use {@link #closeDatabase(liquibase.database.Database)} instead.
     */
    @Deprecated
    protected void closeDatabase(Liquibase liquibase) {
        if ((liquibase != null) && (liquibase.getDatabase() != null) && (liquibase.getDatabase().getConnection() !=
            null)) {
            try {
                liquibase.getDatabase().close();
            } catch (DatabaseException e) {
                log("Error closing database: " + e.getMessage());
            }
        }
    }

    /**
     * @deprecated Use {@link DatabaseType#getDatabaseClass()} instead.
     */
    @Deprecated
    public String getDatabaseClass() {
        return getDatabaseType().getDatabaseClass();
    }

    /**
     * @deprecated Use {@link DatabaseType#setDatabaseClass(String)} instead.
     */
    @Deprecated
    public void setDatabaseClass(String databaseClass) {
        log("The databaseClass attribute is deprecated. Use a nested <database> element or set the databaseRef attribute instead.", Project.MSG_WARN);
        getDatabaseType().setDatabaseClass(databaseClass);
    }

    /**
     * @deprecated Use {@link DatabaseType#getDatabaseChangeLogTableName()} instead.
     */
    @Deprecated
    public String getDatabaseChangeLogTableName() {
        return getDatabaseType().getDatabaseChangeLogTableName();
    }

    /**
     * @deprecated Use {@link DatabaseType#setDatabaseChangeLogTableName(String)} instead.
     */
    @Deprecated
    public void setDatabaseChangeLogTableName(String tableName) {
        log("The databaseChangeLogTableName attribute is deprecated. Use a nested <database> element or set the databaseRef attribute instead.", Project.MSG_WARN);
        getDatabaseType().setDatabaseChangeLogTableName(tableName);
    }

    /**
     * @deprecated Use {@link DatabaseType#getDatabaseChangeLogLockTableName()} instead.
     */
    @Deprecated
    public String getDatabaseChangeLogLockTableName() {
        return getDatabaseType().getDatabaseChangeLogLockTableName();
    }

    @Deprecated
    public void setDatabaseChangeLogLockTableName(String tableName) {
        log("The databaseChangeLogLockTableName attribute is deprecated. Use a nested <database> element or set the databaseRef attribute instead.", Project.MSG_WARN);
        databaseType.setDatabaseChangeLogLockTableName(tableName);
    }

    /**
     * @deprecated Use {@link DatabaseType#getLiquibaseTablespaceName()} instead.
     */
    @Deprecated
    public String getDatabaseChangeLogObjectsTablespace() {
        return databaseType.getLiquibaseTablespaceName();
    }

    /**
     * @deprecated Use {@link DatabaseType#setLiquibaseTablespaceName(String)} instead.
     */
    @Deprecated
    public void setDatabaseChangeLogObjectsTablespace(String tablespaceName) {
        log("The databaseChangeLogObjectsTablespace attribute is deprecated. Use a nested <database> element or set the databaseRef attribute instead.", Project.MSG_WARN);
        getDatabaseType().setLiquibaseTablespaceName(tablespaceName);
    }

    /**
     * @deprecated Use {@link DatabaseType#isOutputDefaultSchema()} instead.
     */
    @Deprecated
    public boolean isOutputDefaultSchema() {
        return getDatabaseType().isOutputDefaultSchema();
    }

    /**
     * If not set, defaults to true.
     *
     * @deprecated Use a nested {@link DatabaseType DatabaseType} instead.
     * @param outputDefaultSchema True to output the default schema.
     */
    @Deprecated
    public void setOutputDefaultSchema(boolean outputDefaultSchema) {
        log("The outputDefaultSchema attribute is deprecated. Use a nested <database> element or set the databaseRef attribute instead.", Project.MSG_WARN);
        getDatabaseType().setOutputDefaultSchema(outputDefaultSchema);
    }

    /**
     * @deprecated Use {@link DatabaseType#isOutputDefaultCatalog()} instead.
     */
    @Deprecated
    public boolean isOutputDefaultCatalog() {
        return getDatabaseType().isOutputDefaultCatalog();
    }

    /**
     * If not set, defaults to true
     *
     * @deprecated Use {@link DatabaseType#setOutputDefaultCatalog(boolean)} instead.
     * @param outputDefaultCatalog True to output the default catalog.
     */
    @Deprecated
    public void setOutputDefaultCatalog(boolean outputDefaultCatalog) {
        log("The outputDefaultCatalog attribute is deprecated. Use a nested <database> element or set the databaseRef attribute instead.", Project.MSG_WARN);
        getDatabaseType().setOutputDefaultCatalog(outputDefaultCatalog);
    }

    /**
     * Redirector of logs from java.util.logging to ANT's logging
     */
    @Deprecated
    protected static class LogRedirector {

        private final Task task;

        /**
         * Constructor
         *
         * @param task Ant task
         */
        protected LogRedirector(Task task) {
            super();
            this.task = task;
        }

        protected void redirectLogger() {
            registerHandler(createHandler());
        }

        protected void registerHandler(Handler theHandler) {
            Logger logger = Scope.getCurrentScope().getLog(getClass());
        }

        protected Handler createHandler() {
            return new Handler() {
                @Override
                public void publish(LogRecord logRecord) {
                    task.log(logRecord.getMessage(), mapLevelToAntLevel(logRecord.getLevel()));
                }

                @Override
                public void close() throws SecurityException {
                }

                @Override
                public void flush() {
                }

                protected int mapLevelToAntLevel(Level level) {
                    if (Level.ALL == level) {
                        return Project.MSG_INFO;
                    } else if (Level.SEVERE == level) {
                        return Project.MSG_ERR;
                    } else if (Level.WARNING == level) {
                        return Project.MSG_WARN;
                    } else if (Level.INFO == level) {
                        return Project.MSG_INFO;
                    } else {
                        return Project.MSG_VERBOSE;
                    }
                }
            };
        }

    }

    /**
     * @deprecated Use {@link ChangeLogParametersType} instead.
     */
    @Deprecated
    public static class ChangeLogProperty {
        private String name;
        private String value;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }
    }
}


package liquibase.util.csv.opencsv;

/**
 Copyright 2005 Bytecode Pty Ltd.

 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.
 */

import liquibase.util.StringUtil;
import liquibase.util.csv.opencsv.enums.CSVReaderNullFieldIndicator;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import static liquibase.util.csv.opencsv.enums.CSVReaderNullFieldIndicator.NEITHER;

/**
 * A very simple CSV parser released under a commercial-friendly license.
 * This just implements splitting a single line into fields.
 *
 * @author Glen Smith
 * @author Rainer Pruy
 */
public class CSVParser {

    /**
     * The default separator to use if none is supplied to the constructor.
     */
    public static final char DEFAULT_SEPARATOR = ',';
    /**
     * The average size of a line read by openCSV (used for setting the size of StringBuilders).
     */
    public static final int INITIAL_READ_SIZE = 1024;
    /**
     * In most cases we know the size of the line we want to read.  In that case we will set the initial read
     * to that plus an buffer size.
     */
    public static final int READ_BUFFER_SIZE = 128;
    /**
     * The default quote character to use if none is supplied to the
     * constructor.
     */
    public static final char DEFAULT_QUOTE_CHARACTER = '"';
    /**
     * The default escape character to use if none is supplied to the
     * constructor.
     */
    public static final char DEFAULT_ESCAPE_CHARACTER = '\\';
    /**
     * The default strict quote behavior to use if none is supplied to the
     * constructor.
     */
    public static final boolean DEFAULT_STRICT_QUOTES = false;

    /**
     * If the quote character is set to null then there is no quote character.
     */
    public static final boolean DEFAULT_IGNORE_QUOTATIONS = false;
    /**
     * This is the "null" character - if a value is set to this then it is ignored.
     */
    public static final char NULL_CHARACTER = '\0';
    /**
     * Denotes what field contents will cause the parser to return null:  EMPTY_SEPARATORS, EMPTY_QUOTES, BOTH, NEITHER (default)
     */
    public static final CSVReaderNullFieldIndicator DEFAULT_NULL_FIELD_INDICATOR = NEITHER;

    /**
     * This is the character that the CSVParser will treat as the separator.
     */
    private final char separator;
    /**
     * This is the character that the CSVParser will treat as the quotation character.
     */
    private final char quotechar;
    /**
     * This is the character that the CSVParser will treat as the escape character.
     */
    private final char escape;
    /**
     * Determines if the field is between quotes (true) or between separators (false).
     */
    private final boolean strictQuotes;
    /**
     * Ignore any leading white space at the start of the field.
     */
    private final boolean ignoreLeadingWhiteSpace;
    /**
     * Skip over quotation characters when parsing.
     */
    private final boolean ignoreQuotations;
    private final CSVReaderNullFieldIndicator nullFieldIndicator;
    private String pending;
    private boolean inField;

    /**
     * Constructs CSVParser using a comma for the separator.
     */
    public CSVParser() {
        this(DEFAULT_SEPARATOR, DEFAULT_QUOTE_CHARACTER, DEFAULT_ESCAPE_CHARACTER);
    }

    /**
     * Constructs CSVParser with supplied separator.
     *
     * @param separator the delimiter to use for separating entries.
     */
    public CSVParser(char separator) {
        this(separator, DEFAULT_QUOTE_CHARACTER, DEFAULT_ESCAPE_CHARACTER);
    }

    /**
     * Constructs CSVParser with supplied separator and quote char.
     *
     * @param separator the delimiter to use for separating entries
     * @param quotechar the character to use for quoted elements
     */
    public CSVParser(char separator, char quotechar) {
        this(separator, quotechar, DEFAULT_ESCAPE_CHARACTER);
    }

    /**
     * Constructs CSVReader with supplied separator and quote char.
     *
     * @param separator the delimiter to use for separating entries
     * @param quotechar the character to use for quoted elements
     * @param escape    the character to use for escaping a separator or quote
     */
    public CSVParser(char separator, char quotechar, char escape) {
        this(separator, quotechar, escape, DEFAULT_STRICT_QUOTES);
    }

    /**
     * Constructs CSVParser with supplied separator and quote char.
     * Allows setting the "strict quotes" flag
     *
     * @param separator    the delimiter to use for separating entries
     * @param quotechar    the character to use for quoted elements
     * @param escape       the character to use for escaping a separator or quote
     * @param strictQuotes if true, characters outside the quotes are ignored
     */
    public CSVParser(char separator, char quotechar, char escape, boolean strictQuotes) {
        this(separator, quotechar, escape, strictQuotes, false);
    }

    /**
     * Constructs CSVParser with supplied separator and quote char.
     * Allows setting the "strict quotes" and "ignore leading whitespace" flags
     *
     * @param separator               the delimiter to use for separating entries
     * @param quotechar               the character to use for quoted elements
     * @param escape                  the character to use for escaping a separator or quote
     * @param strictQuotes            if true, characters outside the quotes are ignored
     * @param ignoreLeadingWhiteSpace if true, white space in front of a quote in a field is ignored
     */
    public CSVParser(char separator, char quotechar, char escape, boolean strictQuotes, boolean ignoreLeadingWhiteSpace) {
        this(separator, quotechar, escape, strictQuotes, ignoreLeadingWhiteSpace, DEFAULT_IGNORE_QUOTATIONS);
    }

    /**
     * Constructs CSVParser with supplied separator and quote char.
     * Allows setting the "strict quotes" and "ignore leading whitespace" flags
     *
     * @param separator               the delimiter to use for separating entries
     * @param quotechar               the character to use for quoted elements
     * @param escape                  the character to use for escaping a separator or quote
     * @param strictQuotes            if true, characters outside the quotes are ignored
     * @param ignoreLeadingWhiteSpace if true, white space in front of a quote in a field is ignored
     * @param ignoreQuotations        if true, treat quotations like any other character.
     */
    public CSVParser(char separator, char quotechar, char escape, boolean strictQuotes, boolean ignoreLeadingWhiteSpace,
                     boolean ignoreQuotations) {
        this(separator, quotechar, escape, strictQuotes, ignoreLeadingWhiteSpace, ignoreQuotations, DEFAULT_NULL_FIELD_INDICATOR);
    }

    /**
     * Constructs CSVParser with supplied separator and quote char.
     * Allows setting the "strict quotes" and "ignore leading whitespace" flags
     *
     * @param separator               the delimiter to use for separating entries
     * @param quotechar               the character to use for quoted elements
     * @param escape                  the character to use for escaping a separator or quote
     * @param strictQuotes            if true, characters outside the quotes are ignored
     * @param ignoreLeadingWhiteSpace if true, white space in front of a quote in a field is ignored
     * @param ignoreQuotations        if true, treat quotations like any other character.
     * @param nullFieldIndicator      which field content will be returned as null: EMPTY_SEPARATORS, EMPTY_QUOTES,
     *                                BOTH, NEITHER (default)
     */
    CSVParser(char separator, char quotechar, char escape, boolean strictQuotes, boolean ignoreLeadingWhiteSpace,
              boolean ignoreQuotations, CSVReaderNullFieldIndicator nullFieldIndicator) {
        if (anyCharactersAreTheSame(separator, quotechar, escape)) {
            throw new UnsupportedOperationException("The separator, quote, and escape characters must be different!");
        }
        if (separator == NULL_CHARACTER) {
            throw new UnsupportedOperationException("The separator character must be defined!");
        }
        this.separator = separator;
        this.quotechar = quotechar;
        this.escape = escape;
        this.strictQuotes = strictQuotes;
        this.ignoreLeadingWhiteSpace = ignoreLeadingWhiteSpace;
        this.ignoreQuotations = ignoreQuotations;
        this.nullFieldIndicator = nullFieldIndicator;
    }

    /**
     * @return The default separator for this parser.
     */
    public char getSeparator() {
        return separator;
    }

    /**
     * @return The default quotation character for this parser.
     */
    public char getQuotechar() {
        return quotechar;
    }

    /**
     * @return The default escape character for this parser.
     */
    public char getEscape() {
        return escape;
    }

    /**
     * @return The default strictQuotes setting for this parser.
     */
    public boolean isStrictQuotes() {
        return strictQuotes;
    }

    /**
     * @return The default ignoreLeadingWhiteSpace setting for this parser.
     */
    public boolean isIgnoreLeadingWhiteSpace() {
        return ignoreLeadingWhiteSpace;
    }

    /**
     * @return the default ignoreQuotation setting for this parser.
     */
    public boolean isIgnoreQuotations() {
        return ignoreQuotations;
    }

    /**
     * checks to see if any two of the three characters are the same.  This is because in openCSV the
     * separator, quote, and escape characters must the different.
     *
     * @param separator the defined separator character
     * @param quotechar the defined quotation cahracter
     * @param escape    the defined escape character
     * @return true if any two of the three are the same.
     */
    private boolean anyCharactersAreTheSame(char separator, char quotechar, char escape) {
        return isSameCharacter(separator, quotechar) || isSameCharacter(separator, escape) || isSameCharacter(quotechar, escape);
    }

    /**
     * checks that the two characters are the same and are not the defined NULL_CHARACTER.
     * @param c1 first character
     * @param c2 second character
     * @return true if both characters are the same and are not the defined NULL_CHARACTER
     */
    private boolean isSameCharacter(char c1, char c2) {
        return (c1 != NULL_CHARACTER) && (c1 == c2);
    }

    /**
     * @return true if something was left over from last call(s)
     */
    public boolean isPending() {
        return pending != null;
    }

    /**
     * Parses an incoming String and returns an array of elements.  This method is used when the
     * data spans multiple lines.
     *
     * @param nextLine current line to be processed
     * @return the comma-tokenized list of elements, or null if nextLine is null
     * @throws IOException if bad things happen during the read
     */
    public String[] parseLineMulti(String nextLine) throws IOException {
        return parseLine(nextLine, true);
    }

    /**
     * Parses an incoming String and returns an array of elements.  This method is used when all data is contained
     * in a single line.
     *
     * @param nextLine Line to be parsed.
     * @return the comma-tokenized list of elements, or null if nextLine is null
     * @throws IOException if bad things happen during the read
     */
    public String[] parseLine(String nextLine) throws IOException {
        return parseLine(nextLine, false);
    }

    /**
     * Parses an incoming String and returns an array of elements.
     *
     * @param nextLine the string to parse
     * @param multi Does it take multiple lines to form a single record.
     * @return the comma-tokenized list of elements, or null if nextLine is null
     * @throws IOException if bad things happen during the read
     */
    protected String[] parseLine(String nextLine, boolean multi) throws IOException {

        if (!multi && (pending != null)) {
            pending = null;
        }

        if (nextLine == null) {
            if (pending != null) {
                String s = pending;
                pending = null;
                return new String[]{s};
            } else {
                return null;
            }
        }

        List<String> tokensOnThisLine = new ArrayList<>();
        StringBuilder sb = new StringBuilder(nextLine.length() + READ_BUFFER_SIZE);
        boolean inQuotes = false;
        boolean fromQuotedField = false;
        if (pending != null) {
            sb.append(pending);
            pending = null;
            inQuotes = !this.ignoreQuotations;//true;
        }
        for (int i = 0; i < nextLine.length(); i++) {

            char c = nextLine.charAt(i);
            if (c == this.escape) {
                if (isNextCharacterEscapable(nextLine, inQuotes(inQuotes), i)) {
                    i = appendNextCharacterAndAdvanceLoop(nextLine, sb, i);
                }
            } else if (c == quotechar) {
                if (isNextCharacterEscapedQuote(nextLine, inQuotes(inQuotes), i)) {
                    i = appendNextCharacterAndAdvanceLoop(nextLine, sb, i);
                } else {

                    inQuotes = !inQuotes;
                    if (atStartOfField(sb)) {
                        fromQuotedField = true;
                    }

                    // the tricky case of an embedded quote in the middle: a,bc"d"ef,g
                    if (!strictQuotes) {
                        if ((i > 2) //not on the beginning of the line
                            && (nextLine.charAt(i - 1) != this.separator) //not at the beginning of an escape sequence
                            && (nextLine.length() > (i + 1)) && (nextLine.charAt(i + 1) != this.separator) //not at the	end of an escape sequence
                                ) {

                            if (ignoreLeadingWhiteSpace && (sb.length() > 0) && isAllWhiteSpace(sb)) {
                                sb.setLength(0);
                            } else {
                                sb.append(c);
                            }

                        }
                    }
                }
                inField = !inField;
            } else if ((c == separator) && !(inQuotes && !ignoreQuotations)) {
                tokensOnThisLine.add(convertEmptyToNullIfNeeded(sb.toString(), fromQuotedField));
                fromQuotedField = false;
                sb.setLength(0);
                inField = false;
            } else {
                if (!strictQuotes || (inQuotes && !ignoreQuotations)) {
                    sb.append(c);
                    inField = true;
                    fromQuotedField = true;
                }
            }

        }
        // line is done - check status
        if ((inQuotes && !ignoreQuotations)) {
            if (multi) {
                // continuing a quoted section, re-append newline
                sb.append('\n');
                pending = sb.toString();
                sb = null; // this partial content is not to be added to field list yet
            } else {
                throw new IOException("Un-terminated quoted field at end of CSV line");
            }
            if (inField) {
                fromQuotedField = true;
            }
        } else {
            inField = false;
        }

        if (sb != null) {
            tokensOnThisLine.add(convertEmptyToNullIfNeeded(sb.toString(), fromQuotedField));
            fromQuotedField = false;
        }
        return tokensOnThisLine.toArray(new String[tokensOnThisLine.size()]);

    }

    private boolean atStartOfField(StringBuilder sb) {
        return sb.length() == 0;
    }

    private String convertEmptyToNullIfNeeded(String s, boolean fromQuotedField) {
        if (s.isEmpty() && shouldConvertEmptyToNull(fromQuotedField)) {
            return null;
        }
        return s;
    }

    private boolean shouldConvertEmptyToNull(boolean fromQuotedField) {
        switch (nullFieldIndicator) {
            case BOTH:
                return true;
            case EMPTY_SEPARATORS:
                return !fromQuotedField;
            case EMPTY_QUOTES:
                return fromQuotedField;
            default:
                return false;
        }
    }

    /**
     * Appends the next character in the line to the stringbuffer.
     *
     * @param line - line to process
     * @param sb   - contains the processed character
     * @param i    - current position in the line.
     * @return new position in the line.
     */
    private int appendNextCharacterAndAdvanceLoop(String line, StringBuilder sb, int i) {
        sb.append(line.charAt(i + 1));
        i++;
        return i;
    }

    /**
     * Determines if we can process as if we were in quotes.
     *
     * @param inQuotes - are we currently in quotes.
     * @return - true if we should process as if we are inside quotes.
     */
    private boolean inQuotes(boolean inQuotes) {
        return (inQuotes && !ignoreQuotations) || inField;
    }

    /**
     * Checks to see if the character after the index is a quotation character.
     *
     * precondition: the current character is a quote or an escape
     *
     * @param nextLine the current line
     * @param inQuotes true if the current context is quoted
     * @param i        current index in line
     * @return true if the following character is a quote
     */
    private boolean isNextCharacterEscapedQuote(String nextLine, boolean inQuotes, int i) {
        return inQuotes  // we are in quotes, therefore there can be escaped quotes in here.
            && (nextLine.length() > (i + 1))  // there is indeed another character to check.
            && isCharacterQuoteCharacter(nextLine.charAt(i + 1));
    }

    /**
     * Checks to see if the passed in character is the defined quotation character.
     *
     * @param c source character
     * @return true if c is the defined quotation character
     */
    private boolean isCharacterQuoteCharacter(char c) {
        return c == quotechar;
    }

    /**
     * checks to see if the character is the defined escape character.
     *
     * @param c source character
     * @return true if the character is the defined escape character
     */
    private boolean isCharacterEscapeCharacter(char c) {
        return c == escape;
    }

    /**
     * Checks to see if the character passed in could be escapable.  Escapable characters for openCSV are the
     * quotation character or the escape character.
     *
     * @param c source character
     * @return true if the character could be escapable.
     */
    private boolean isCharacterEscapable(char c) {
        return isCharacterQuoteCharacter(c) || isCharacterEscapeCharacter(c);
    }

    /**
     * Checks to see if the character after the current index in a String is an escapable character.
     * Meaning the next character is either a quotation character or the escape char and you are inside
     * quotes.
     *
     * precondition: the current character is an escape
     *
     * @param nextLine the current line
     * @param inQuotes true if the current context is quoted
     * @param i        current index in line
     * @return true if the following character is a quote
     */
    protected boolean isNextCharacterEscapable(String nextLine, boolean inQuotes, int i) {
        return inQuotes  // we are in quotes, therefore there can be escaped quotes in here.
            && (nextLine.length() > (i + 1))  // there is indeed another character to check.
            && isCharacterEscapable(nextLine.charAt(i + 1));
    }

    /**
     * Checks if every element is the character sequence is whitespace.
     *
     * precondition: sb.length() is greater than 0
     *
     * @param sb A sequence of characters to examine
     * @return true if every character in the sequence is whitespace
     */
    protected boolean isAllWhiteSpace(CharSequence sb) {
        return StringUtil.isWhitespace(sb);
    }

    /**
     * @return - the null field indicator.
     */
    public CSVReaderNullFieldIndicator nullFieldIndicator() {
        return nullFieldIndicator;
    }
}


package liquibase.parser.core;

import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.statement.DatabaseFunction;
import liquibase.statement.SequenceCurrentValueFunction;
import liquibase.statement.SequenceNextValueFunction;
import liquibase.util.ISODateFormat;
import liquibase.util.StringUtil;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;

/**
 * Acts as a standard abstract syntax layer for changelogs defined in different formats.
 * {@link liquibase.parser.ChangeLogParser} implementations and other classes that work with multiple formats can create objects
 * directs or create instances of this class which can then be passed to the load() method of the object they want to configure.
 * For example, {@link liquibase.change.Change#load(ParsedNode, liquibase.resource.ResourceAccessor)}.
 *

 * ParsedNodes are a simple key/value structure with the following characteristics:
 *

 *
Keys include a namespace as well as the node name

 *
There can be multiple children nodes with the same node namespace+name

 *
There is an unkeyed "value" object in addition to the children nodes

 *
The value node cannot be a ParsedNode. If you attempt to set value to be or contain a ParsedNode it will actually be set as a child

 *

 */
public class ParsedNode {
    private String namespace;
    private String name;
    private List<ParsedNode> children = new ArrayList<>();
    private Object value;

    public ParsedNode(String namespace, String name) {
        this.namespace = namespace;
        this.name = name;
    }

    /**
     * Each node key contains both a namespace and a name which together identifies the node.
     */
    public String getNamespace() {
        return namespace;
    }

    /**
     * Each node key contains both a namespace and a name which together identifies the node.
     */
    public String getName() {
        return name;
    }

    /**
     * Returns the child ParsedNodes of this node. Returned list is unmodifiableList.
     */
    public List<ParsedNode> getChildren() {
        return Collections.unmodifiableList(children);
    }

    /**
     * Returns all child nodes with the given namespace and name.
     * If none match, an empty list is returned.
     * Returned list is unmodifiableList.
     */
    public List<ParsedNode> getChildren(String namespace, String nodename) {
        List<ParsedNode> returnList = new ArrayList<>();
        for (ParsedNode node : children) {
            if (nodeMatches(node, namespace, nodename)) {
                returnList.add(node);
            }
        }
        return Collections.unmodifiableList(returnList);
    }

    /**
     * Return the value associated with this node.
     */
    public Object getValue() {
        return value;
    }

    /**
     * Sets the value of this ParsedNode.
     * If the passed value is a ParsedNode, it is added as a child, not as the value.
     * If the passed value is a Map, it is converted to a ParsedNode and added as a child, not as the value.
     * If the passed value is a Collection, each object is added as a child if it is a ParsedNode or a Map.
     * If there are multiple simple values in a passed collection, value is set to a List. If there is a single value in a collection, value is set to the single value.
     */
    public ParsedNode setValue(Object value) throws ParsedNodeException {
        if (value instanceof ParsedNode) {
            this.addChild((ParsedNode) value);
        } else if (value instanceof Collection) {
            List newValue = new ArrayList();
            for (Object obj : ((Collection) value)) {
                if (obj instanceof Map) {
                    addChildren((Map) obj);
                } else if (obj instanceof ParsedNode) {
                    addChild(((ParsedNode) obj));
                } else {
                    newValue.add(obj);
                }
            }
            if (newValue.isEmpty()) {
                //do nothing
            } else if (newValue.size() == 1) {
                this.value = newValue.get(0);
            } else {
                this.value = newValue;
            }
        } else if (value instanceof Map) {
            addChildren(((Map) value));
        } else {
            this.value = value;
        }
        return this;
    }

    /**
     * Return the value associated with this node converted to the given type.
     *
     * @throws ParsedNodeException if the current value type cannot be converted
     */
    public <T> T getValue(Class<T> type) throws ParsedNodeException {
        return convertObject(value, type);
    }

    /**
     * Convenience method to add a new ParsedNode with the passed namespace/name and value
     */
    public ParsedNode addChild(String namespace, String nodeName, Object value) throws ParsedNodeException {
        addChild(createNode(namespace, nodeName).setValue(value));
        return this;
    }

    protected ParsedNode createNode(String namespace, String nodeName) {
        return new ParsedNode(namespace, nodeName);
    }

    /**
     * Adds the given ParsedNode as a child
     */
    public ParsedNode addChild(ParsedNode node) throws ParsedNodeException {
        children.add(node);
        return this;
    }

    /**
     * Adds the given map as children of this node.
     * If the passed map is empty, it is a no-op
     * For each key in the map, a new child is added with the key as the name and the value (with all {@link #setValue(Object)}) logic) is the value.
     */
    public ParsedNode addChildren(Map<String, Object> child) throws ParsedNodeException {
        if ((child == null) || child.isEmpty()) {
            return this; //do nothing
        }
        for (Map.Entry<String, Object> entry : child.entrySet()) {
            this.addChild(null, entry.getKey(), entry.getValue());
        }

        return this;
    }

    /**
     * Returns the ParsedNode defined by the given namespace and name.
     * @throws liquibase.parser.core.ParsedNodeException if multiple nodes match.
     */
    public ParsedNode getChild(String namespace, String name) throws ParsedNodeException {
        ParsedNode returnNode = null;
        for (ParsedNode node : children) {
            if (nodeMatches(node, namespace, name)) {
                if (returnNode != null) {
                    throw new ParsedNodeException("Multiple nodes match "+namespace+"/"+name);
                }
                returnNode = node;
            }
        }
        return returnNode;
    }

    public ParsedNode removeChild(String namespace, String name) throws ParsedNodeException {
        ListIterator<ParsedNode> iterator = children.listIterator();
        while (iterator.hasNext()) {
            ParsedNode node = iterator.next();
            if (nodeMatches(node, namespace, name)) {
                iterator.remove();
            }
        }
        return this;
    }

    protected boolean nodeMatches(ParsedNode node, String namespace, String nodename) {
        return namespaceMatches(node, namespace) && node.getName().equals(nodename);
    }

    protected boolean namespaceMatches(ParsedNode node, String namespace) {
        if (node.getNamespace() == null) {
            return namespace == null;
        }
        return node.getNamespace().equals(namespace);
    }

    /**
     * Convenience method for {@link #getChildValue(String, String, Class)} but returns the passed defaultValue if the given node is null or not defined.
     */
    public <T> T getChildValue(String namespace, String nodename, T defaultValue) throws ParsedNodeException {
        T value = (T) getChildValue(namespace, nodename, defaultValue.getClass());
        if (value == null) {
            return defaultValue;
        }
        return value;
    }

    /**
     * Returns the value of the given node, converted to the passed type.
     * @throws liquibase.parser.core.ParsedNodeException if there is an error converting the value
     */
    public <T> T getChildValue(String namespace, String nodename, Class<T> type) throws ParsedNodeException {
        Object rawValue = getChildValue(namespace, nodename);
        if (rawValue == null) {
            return null;
        }
        return convertObject(rawValue, type);
    }

    protected <T> T convertObject(Object rawValue, Class<T> type) throws ParsedNodeException {
        if (rawValue == null) {
            return null;
        }
        if (type.isAssignableFrom(rawValue.getClass())) {
            return (T) rawValue;
        }

        try {
            if (type.equals(String.class)) {
                return (T) rawValue.toString();
            } else if (type.equals(Integer.class)) {
                return (T) Integer.valueOf(rawValue.toString());
            } else if (type.equals(Short.class)) {
                return (T) Short.valueOf(rawValue.toString());
            } else if (type.equals(Float.class)) {
                return (T) Float.valueOf(rawValue.toString());
            } else if (type.equals(Double.class)) {
                return (T) Double.valueOf(rawValue.toString());
            } else if (type.equals(Long.class)) {
                return (T) Long.valueOf(rawValue.toString());
            } else if (type.equals(BigInteger.class)) {
                return (T) new BigInteger(rawValue.toString());
            } else if (type.equals(BigDecimal.class)) {
                return (T) new BigDecimal(rawValue.toString());
            } else if (type.equals(Boolean.class) && (rawValue instanceof String)) {
                return (T) Boolean.valueOf(rawValue.toString());
            } else if (type.isAssignableFrom(Date.class)) {
                return (T) new ISODateFormat().parse(rawValue.toString());
            } else if (type.equals(SequenceNextValueFunction.class)) {
                return (T) new SequenceNextValueFunction(rawValue.toString());
            } else if (type.equals(SequenceCurrentValueFunction.class)) {
                return (T) new SequenceCurrentValueFunction(rawValue.toString());
            } else if (type.equals(DatabaseFunction.class)) {
                return (T) new DatabaseFunction(rawValue.toString());
            } else if (type.isEnum()) {
                return (T) Enum.valueOf((Class<Enum>)type, rawValue.toString());
            } else {
                throw new UnexpectedLiquibaseException("Cannot convert " + rawValue.getClass().getName() + " '" + rawValue + "' to " + type.getName());
            }
        } catch (Exception e) {
            if (e instanceof UnexpectedLiquibaseException) {
                throw (UnexpectedLiquibaseException) e;
            }
            throw new ParsedNodeException(e);
        }
    }

    /**
     * Returns the value of the given node with no conversion attempted.
     */
    public Object getChildValue(String namespace, String nodename) throws ParsedNodeException {
        ParsedNode child = getChild(namespace, nodename);
        if (child == null) {
            return null;
        }
        return child.getValue();
    }

    @Override
    public String toString() {
        String string = name;
        if (!children.isEmpty()) {
            string += "[" + StringUtil.join(children, ",", new StringUtil.ToStringFormatter(), true) + "]";
        }
        if (value != null) {
            String valueString;
            if (value instanceof Collection) {
                valueString = "("+ StringUtil.join(((Collection) value), ",", new StringUtil.ToStringFormatter(), true)+")";
            } else {
                valueString = value.toString();
            }
            string += "="+valueString;
        }
        return string;
    }

    @Override
    public boolean equals(Object obj) {
        return (obj instanceof ParsedNode) && this.toString().equals(obj.toString());
    }

    @Override
    public int hashCode() {
        return this.toString().hashCode();
    }
}


package liquibase.resource;

import liquibase.Scope;
import liquibase.logging.Logger;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.*;
import java.util.function.Consumer;

/**
 * A list of {@link InputStream}s. Custom class to allow try-with-resources using output from {@link ResourceAccessor#openStreams(String, String)}.
 */
public class InputStreamList implements Iterable<InputStream>, AutoCloseable {

    private LinkedHashMap<URI, InputStream> streams = new LinkedHashMap<>();

    public InputStreamList() {
    }

    public InputStreamList(URI uri, InputStream stream) {
        this.streams.put(uri, stream);
    }

    public boolean add(URI uri, InputStream inputStream) {
        Logger log = Scope.getCurrentScope().getLog(getClass());

        boolean duplicate = streams.put(uri, inputStream) != null;
        if (duplicate) {
            log.fine("Closing duplicate stream for "+uri);
            try {
                inputStream.close();
            } catch (IOException e) {
                log.warning("Cannot close stream for "+uri, e);
            }
        }
        return duplicate;
    }

    public void addAll(InputStreamList streams) {
        if (streams == null) {
            return;
        }
        this.streams.putAll(streams.streams);
    }

    /**
     * Close the streams in this collection.
     */
    @Override
    public void close() throws IOException {
        for (InputStream stream : this) {
            try {
                stream.close();
            } catch (IOException e) {
                Scope.getCurrentScope().getLog(getClass()).severe("Error closing stream. Logging error and continuing", e);
            }
        }
    }

    @Override
    public Iterator<InputStream> iterator() {
        return streams.values().iterator();
    }

    @Override
    public void forEach(Consumer<? super InputStream> action) {
        streams.values().forEach(action);
    }

    @Override
    public Spliterator<InputStream> spliterator() {
        return streams.values().spliterator();
    }

    public int size() {
        return streams.size();
    }

    public boolean isEmpty() {
        return streams.isEmpty();
    }

    public List<URI> getURIs() {
        return new ArrayList<>(streams.keySet());
    }
}


package liquibase.datatype;

import liquibase.util.StringUtil;

import java.util.Locale;

/**
 * This class represents a native data type used by a specific RDBMS. This is in contrast of
 * {@link LiquibaseDataType}, which represents data types used in changeSets (which will later be translated into
 * the RDBMS-specific data type if required).
 */
public class DatabaseDataType {

    private String type;

    public DatabaseDataType(String type) {
        this.type = type;
    }

    public DatabaseDataType(String name, Object... parameters) {
        if (parameters == null) {
            parameters = new Object[0];
        }
        this.type = name;

        String[] stringParams = new String[parameters.length];
        if (parameters.length > 0) {
            for (int i = 0; i<parameters.length; i++){
                if (parameters[i] == null) {
                    stringParams[i] = "NULL";
                } else {
                    stringParams[i] = parameters[i].toString();
                }
            }
            type += "("+ StringUtil.join(stringParams, ", ")+")";
        }
    }

    public void addAdditionalInformation(String additionalInformation) {
        if (additionalInformation != null) {
            this.type += " " + additionalInformation;
        }
    }

    /**
     * Mainly for postgres, check if the column is a serial data type.
     * @return Whether the type is serial
     */
    public boolean isAutoIncrement() {
        return "serial".equals(type.toLowerCase(Locale.US)) || "bigserial".equals(type.toLowerCase(Locale.US)) || "smallserial"
            .equals(type.toLowerCase(Locale.US));
    }

    public String toSql() {
        return toString();
    }

    @Override
    public String toString() {
        return type;
    }

    public String getType() {
        return type;
    }

    public void setType(final String type) {
        this.type = type;
    }

}