com.panet.imeta.job.JobMeta.java Source code

Java tutorial

Introduction

Here is the source code for com.panet.imeta.job.JobMeta.java

Source

/* Copyright (c) 2007 Pentaho Corporation.  All rights reserved. 
 * This software was developed by Pentaho Corporation and is provided under the terms 
 * of the GNU Lesser General Public License, Version 2.1. You may not use 
 * this file except in compliance with the license. If you need a copy of the license, 
 * please go to http://www.gnu.org/licenses/lgpl-2.1.txt. The Original Code is Pentaho 
 * Data Integration.  The Initial Developer is Pentaho Corporation.
 *
 * Software distributed under the GNU Lesser Public License is distributed on an "AS IS" 
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or  implied. Please refer to 
 * the license for the specific language governing your rights and limitations.*/

package com.panet.imeta.job;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.vfs.FileName;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

import com.panet.imeta.cluster.SlaveServer;
import com.panet.imeta.core.CheckResultInterface;
import com.panet.imeta.core.Const;
import com.panet.imeta.core.EngineMetaInterface;
import com.panet.imeta.core.LastUsedFile;
import com.panet.imeta.core.NotePadMeta;
import com.panet.imeta.core.ProgressMonitorListener;
import com.panet.imeta.core.Props;
import com.panet.imeta.core.RowMetaAndData;
import com.panet.imeta.core.SQLStatement;
import com.panet.imeta.core.changed.ChangedFlag;
import com.panet.imeta.core.database.Database;
import com.panet.imeta.core.database.DatabaseMeta;
import com.panet.imeta.core.exception.KettleDatabaseException;
import com.panet.imeta.core.exception.KettleException;
import com.panet.imeta.core.exception.KettleXMLException;
import com.panet.imeta.core.gui.GUIPositionInterface;
import com.panet.imeta.core.gui.OverwritePrompter;
import com.panet.imeta.core.gui.Point;
import com.panet.imeta.core.gui.UndoInterface;
import com.panet.imeta.core.listeners.FilenameChangedListener;
import com.panet.imeta.core.listeners.NameChangedListener;
import com.panet.imeta.core.logging.LogWriter;
import com.panet.imeta.core.logging.LogWriterInterface;
import com.panet.imeta.core.parameters.NamedParams;
import com.panet.imeta.core.parameters.NamedParamsDefault;
import com.panet.imeta.core.reflection.StringSearchResult;
import com.panet.imeta.core.reflection.StringSearcher;
import com.panet.imeta.core.row.RowMetaInterface;
import com.panet.imeta.core.row.ValueMeta;
import com.panet.imeta.core.undo.TransAction;
import com.panet.imeta.core.util.StringUtil;
import com.panet.imeta.core.variables.VariableSpace;
import com.panet.imeta.core.variables.Variables;
import com.panet.imeta.core.vfs.KettleVFS;
import com.panet.imeta.core.xml.XMLHandler;
import com.panet.imeta.core.xml.XMLInterface;
import com.panet.imeta.job.entries.special.JobEntrySpecial;
import com.panet.imeta.job.entry.JobEntryCopy;
import com.panet.imeta.job.entry.JobEntryInterface;
import com.panet.imeta.repository.Repository;
import com.panet.imeta.repository.RepositoryDirectory;
import com.panet.imeta.repository.RepositoryObject;
import com.panet.imeta.repository.RepositoryUtil;
import com.panet.imeta.resource.ResourceDefinition;
import com.panet.imeta.resource.ResourceExportInterface;
import com.panet.imeta.resource.ResourceNamingInterface;
import com.panet.imeta.resource.ResourceReference;
import com.panet.imeta.shared.SharedObjectInterface;
import com.panet.imeta.shared.SharedObjects;
import com.panet.imeta.trans.HasDatabasesInterface;
import com.panet.imeta.trans.HasSlaveServersInterface;
import com.panet.imeta.trans.step.BaseStepMeta;

/**
 * Defines a Job and provides methods to load, save, verify, etc.
 * 
 * @author Matt
 * @since 11-08-2003
 * 
 */
public class JobMeta extends ChangedFlag
        implements Cloneable, Comparable<JobMeta>, XMLInterface, UndoInterface, HasDatabasesInterface,
        VariableSpace, EngineMetaInterface, ResourceExportInterface, HasSlaveServersInterface {
    public static final String XML_TAG = "job"; //$NON-NLS-1$

    private static final String XML_TAG_SLAVESERVERS = "slaveservers"; //$NON-NLS-1$

    public static final int JOBSTATUS_DRAFT = 0;
    public static final int JOBSTATUS_PRODUCTION = 1;
    public static final String[] jobstatusList = new String[] { Messages.getString("JobMeta.JobStatus.Draft"),
            Messages.getString("JobMeta.JobStatus.Production") };

    public LogWriterInterface log;

    protected long id;

    protected String name;

    protected String description;

    protected String extendedDescription;

    protected String jobVersion;

    protected int jobStatus;

    protected String filename;

    public List<JobEntryInterface> jobentries;

    public List<JobEntryCopy> jobcopies;

    public List<JobHopMeta> jobhops;

    public List<NotePadMeta> notes;

    public List<DatabaseMeta> databases;

    private List<SlaveServer> slaveServers;

    protected RepositoryDirectory directory;

    protected String arguments[];

    protected boolean changedEntries, changedHops, changedNotes, changedDatabases;

    protected DatabaseMeta logConnection;

    protected String logTable;

    protected List<TransAction> undo;

    private VariableSpace variables = new Variables();

    protected int max_undo;

    protected int undo_position;

    private NamedParams namedParams = new NamedParamsDefault();

    public static final int TYPE_UNDO_CHANGE = 1;

    public static final int TYPE_UNDO_NEW = 2;

    public static final int TYPE_UNDO_DELETE = 3;

    public static final int TYPE_UNDO_POSITION = 4;

    public static final String STRING_SPECIAL = "SPECIAL"; //$NON-NLS-1$

    public static final String STRING_SPECIAL_START = "START"; //$NON-NLS-1$

    public static final String STRING_SPECIAL_DUMMY = "DUMMY"; //$NON-NLS-1$

    public static final String STRING_SPECIAL_OK = "OK"; //$NON-NLS-1$

    public static final String STRING_SPECIAL_ERROR = "ERROR"; //$NON-NLS-1$

    // Remember the size and position of the different windows...
    public boolean max[] = new boolean[1];

    private String created_user, modifiedUser;

    private Date created_date, modifiedDate;

    protected boolean useBatchId;

    protected boolean batchIdPassed;

    protected boolean logfieldUsed;

    private int guiLocationX, guiLocationY;

    private double guiScale;

    private Repository rep;

    /**
     * If this is null, we load from the default shared objects file :
     * $KETTLE_HOME/.kettle/shared.xml
     */
    protected String sharedObjectsFile;

    /** The last loaded version of the shared objects */
    private SharedObjects sharedObjects;

    private List<NameChangedListener> nameChangedListeners;

    private List<FilenameChangedListener> filenameChangedListeners;

    public JobMeta(LogWriterInterface l) {
        log = l;
        clear();
        initializeVariablesFrom(null);
    }

    public String getJobMetaTreeSegment(String fileId) {
        StringBuffer ro = new StringBuffer();

        List<DatabaseMeta> dbs = this.getDatabases();
        List<JobEntryCopy> jobEntries = this.getJobEntries();

        ro.append("<li class='closed'><span class='db-folder'>" + Messages.getString("JobMeta.Element.Database")
                + "</span>");
        if (dbs != null && dbs.size() > 0) {
            ro.append("<ul id='" + fileId + "_db'>");
            // dbs
            Iterator<DatabaseMeta> dbIter = dbs.iterator();
            DatabaseMeta db;
            while (dbIter.hasNext()) {
                db = dbIter.next();
                ro.append("<li><span class='element-file " + fileId + "-setting' elementId='" + db.getID()
                        + "' elementType='" + RepositoryObject.STRING_ELEMENT_TYPE_DATABASE + "'>");
                ro.append(db.getName());
                ro.append("</span></li>");
            }
            ro.append("</ul>");
        }
        ro.append("</li>");

        ro.append("<li class='closed'><span class='step-folder'>" + Messages.getString("JobMeta.Element.JobEntry")
                + "</span>");
        if (jobEntries != null && jobEntries.size() > 0) {
            ro.append("<ul id='" + fileId + "_step'>");
            // 
            Iterator<JobEntryCopy> jeIter = jobEntries.iterator();
            JobEntryCopy je;
            while (jeIter.hasNext()) {
                je = jeIter.next();
                ro.append("<li><span class='element-file'>");
                ro.append(je.getName());
                ro.append("</span></li>");
            }
            ro.append("</ul>");
        }
        ro.append("</li>");

        return ro.toString();
    }

    public long getID() {
        return id;
    }

    public void setID(long id) {
        this.id = id;
    }

    public void clear() {
        setName(null);
        setFilename(null);

        jobcopies = new ArrayList<JobEntryCopy>();
        jobentries = new ArrayList<JobEntryInterface>();
        jobhops = new ArrayList<JobHopMeta>();
        notes = new ArrayList<NotePadMeta>();
        databases = new ArrayList<DatabaseMeta>();
        slaveServers = new ArrayList<SlaveServer>();

        logConnection = null;
        logTable = null;
        arguments = null;

        max_undo = Const.MAX_UNDO;

        undo = new ArrayList<TransAction>();
        undo_position = -1;

        addDefaults();
        setChanged(false);

        created_user = "-"; //$NON-NLS-1$
        created_date = new Date();

        modifiedUser = "-"; //$NON-NLS-1$
        modifiedDate = new Date();
        directory = new RepositoryDirectory();
        description = null;
        guiLocationX = 0;
        guiLocationY = 0;
        guiScale = 1;
        jobStatus = -1;
        jobVersion = null;
        extendedDescription = null;
        useBatchId = true;
        logfieldUsed = true;

        // setInternalKettleVariables(); Don't clear the internal variables for
        // ad-hoc jobs, it's ruins the previews
        // etc.
    }

    public void addDefaults() {
        /*
         * addStart(); // Add starting point! addDummy(); // Add dummy! addOK();
         * // errors == 0 evaluation addError(); // errors != 0 evaluation
         */

        clearChanged();
    }

    public static final JobEntryCopy createStartEntry() {
        JobEntrySpecial jobEntrySpecial = new JobEntrySpecial(STRING_SPECIAL_START, true, false);
        JobEntryCopy jobEntry = new JobEntryCopy();
        jobEntry.setID(-1L);
        jobEntry.setEntry(jobEntrySpecial);
        jobEntry.setLocation(50, 50);
        jobEntry.setDrawn(false);
        jobEntry.setDescription(Messages.getString("JobMeta.StartJobEntry.Description")); //$NON-NLS-1$
        return jobEntry;

    }

    public static final JobEntryCopy createDummyEntry() {
        JobEntrySpecial jobEntrySpecial = new JobEntrySpecial(STRING_SPECIAL_DUMMY, false, true);
        JobEntryCopy jobEntry = new JobEntryCopy();
        jobEntry.setID(-1L);
        jobEntry.setEntry(jobEntrySpecial);
        jobEntry.setLocation(50, 50);
        jobEntry.setDrawn(false);
        jobEntry.setDescription(Messages.getString("JobMeta.DummyJobEntry.Description")); //$NON-NLS-1$
        return jobEntry;
    }

    public JobEntryCopy getStart() {
        for (int i = 0; i < nrJobEntries(); i++) {
            JobEntryCopy cge = getJobEntry(i);
            if (cge.isStart())
                return cge;
        }
        return null;
    }

    public JobEntryCopy getDummy() {
        for (int i = 0; i < nrJobEntries(); i++) {
            JobEntryCopy cge = getJobEntry(i);
            if (cge.isDummy())
                return cge;
        }
        return null;
    }

    /**
     * Compares two transformation on name, filename
     */
    public int compare(JobMeta t1, JobMeta t2) {
        if (Const.isEmpty(t1.getName()) && !Const.isEmpty(t2.getName()))
            return -1;
        if (!Const.isEmpty(t1.getName()) && Const.isEmpty(t2.getName()))
            return 1;
        if (Const.isEmpty(t1.getName()) && Const.isEmpty(t2.getName())) {
            if (Const.isEmpty(t1.getFilename()) && !Const.isEmpty(t2.getFilename()))
                return -1;
            if (!Const.isEmpty(t1.getFilename()) && Const.isEmpty(t2.getFilename()))
                return 1;
            if (Const.isEmpty(t1.getFilename()) && Const.isEmpty(t2.getFilename())) {
                return 0;
            }
            return t1.getFilename().compareTo(t2.getFilename());
        }
        return t1.getName().compareTo(t2.getName());
    }

    public int compareTo(JobMeta o) {
        return compare(this, o);
    }

    public boolean equals(Object obj) {
        if (!(obj instanceof JobMeta))
            return false;

        return compare(this, (JobMeta) obj) == 0;
    }

    public Object clone() {
        return realClone(true);
    }

    public Object realClone(boolean doClear) {
        try {
            JobMeta jobMeta = (JobMeta) super.clone();
            if (doClear) {
                jobMeta.clear();
            } else {
                jobMeta.jobcopies = new ArrayList<JobEntryCopy>();
                jobMeta.jobentries = new ArrayList<JobEntryInterface>();
                jobMeta.jobhops = new ArrayList<JobHopMeta>();
                jobMeta.notes = new ArrayList<NotePadMeta>();
                jobMeta.databases = new ArrayList<DatabaseMeta>();
                jobMeta.slaveServers = new ArrayList<SlaveServer>();
            }

            for (JobEntryInterface entry : jobentries)
                jobMeta.jobentries.add((JobEntryInterface) entry.clone());
            for (JobEntryCopy entry : jobcopies)
                jobMeta.jobcopies.add((JobEntryCopy) entry.clone_deep());
            for (JobHopMeta entry : jobhops) {
                JobHopMeta newJobEntryCopy = (JobHopMeta) entry.clone();
                newJobEntryCopy.from_entry = jobMeta.getJobEntry(entry.from_entry.getName());
                newJobEntryCopy.to_entry = jobMeta.getJobEntry(entry.to_entry.getName());
                jobMeta.jobhops.add(newJobEntryCopy);
            }
            for (NotePadMeta entry : notes)
                jobMeta.notes.add((NotePadMeta) entry.clone());
            for (DatabaseMeta entry : databases)
                jobMeta.databases.add((DatabaseMeta) entry.clone());
            for (SlaveServer slave : slaveServers)
                jobMeta.getSlaveServers().add((SlaveServer) slave.clone());

            return jobMeta;
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }

    public String getName() {
        return name;
    }

    /**
     * Set the name of the job.
     * 
     * @param newName
     *            The new name of the job
     */
    public void setName(String newName) {
        fireNameChangedListeners(this.name, newName);
        this.name = newName;
        setInternalKettleVariables();
    }

    /**
     * Builds a name - if no name is set, yet - from the filename
     */
    public void nameFromFilename() {
        if (!Const.isEmpty(filename)) {
            setName(Const.createName(filename));
        }
    }

    /**
     * @return Returns the directory.
     */
    public RepositoryDirectory getDirectory() {
        return directory;
    }

    /**
     * @param directory
     *            The directory to set.
     */
    public void setDirectory(RepositoryDirectory directory) {
        this.directory = directory;
        setInternalKettleVariables();
    }

    public String getFilename() {
        return filename;
    }

    /**
     * Set the filename of the job
     * 
     * @param newFilename
     *            The new filename of the job
     */
    public void setFilename(String newFilename) {
        fireFilenameChangedListeners(this.filename, newFilename);
        this.filename = newFilename;
        setInternalKettleVariables();
    }

    public DatabaseMeta getLogConnection() {
        return logConnection;
    }

    public void setLogConnection(DatabaseMeta ci) {
        logConnection = ci;
    }

    /**
     * @return Returns the databases.
     */
    public List<DatabaseMeta> getDatabases() {
        return databases;
    }

    /**
     * @param databases
     *            The databases to set.
     */
    public void setDatabases(List<DatabaseMeta> databases) {
        Collections.sort(databases, DatabaseMeta.comparator);
        this.databases = databases;
    }

    public void setChanged(boolean ch) {
        if (ch)
            setChanged();
        else
            clearChanged();
    }

    public void clearChanged() {
        changedEntries = false;
        changedHops = false;
        changedNotes = false;
        changedDatabases = false;

        for (int i = 0; i < nrJobEntries(); i++) {
            JobEntryCopy entry = getJobEntry(i);
            entry.setChanged(false);
        }
        for (JobHopMeta hi : jobhops) // Look at all the hops
        {
            hi.setChanged(false);
        }
        for (int i = 0; i < nrDatabases(); i++) {
            DatabaseMeta db = getDatabase(i);
            db.setChanged(false);
        }
        for (int i = 0; i < nrNotes(); i++) {
            NotePadMeta note = getNote(i);
            note.setChanged(false);
        }
        super.clearChanged();
    }

    public boolean hasChanged() {
        if (super.hasChanged())
            return true;

        if (haveJobEntriesChanged())
            return true;
        if (haveJobHopsChanged())
            return true;
        if (haveConnectionsChanged())
            return true;
        if (haveNotesChanged())
            return true;

        return false;
    }

    protected void saveRepJob(Repository rep) throws KettleException {
        try {
            // The ID has to be assigned, even when it's a new item...
            rep.insertJob(getID(), directory.getID(), getName(), logConnection == null ? -1 : logConnection.getID(),
                    logTable, modifiedUser, modifiedDate, useBatchId, batchIdPassed, logfieldUsed,
                    sharedObjectsFile, description, extendedDescription, jobVersion, jobStatus, created_user,
                    created_date, guiLocationX, guiLocationY, guiScale);
        } catch (KettleDatabaseException dbe) {
            throw new KettleException(Messages.getString("JobMeta.Exception.UnableToSaveJobToRepository"), dbe); //$NON-NLS-1$
        }
    }

    public boolean showReplaceWarning(Repository rep) {
        if (getID() < 0) {
            try {
                if (rep.getJobID(getName(), directory.getID()) > 0)
                    return true;
            } catch (KettleException dbe) {
                return true;
            }
        }
        return false;
    }

    /**
     * This method asks all steps in the transformation whether or not the
     * specified database connection is used. The connection is used in the
     * transformation if any of the steps uses it or if it is being used to log
     * to.
     * 
     * @param databaseMeta
     *            The connection to check
     * @return true if the connection is used in this transformation.
     */
    public boolean isDatabaseConnectionUsed(DatabaseMeta databaseMeta) {
        for (int i = 0; i < nrJobEntries(); i++) {
            JobEntryCopy jobEntry = getJobEntry(i);
            DatabaseMeta dbs[] = jobEntry.getEntry().getUsedDatabaseConnections();
            for (int d = 0; d < dbs.length; d++) {
                if (dbs[d] != null && dbs[d].equals(databaseMeta))
                    return true;
            }
        }

        if (logConnection != null && logConnection.equals(databaseMeta))
            return true;

        return false;
    }

    public String getFileType() {
        return LastUsedFile.FILE_TYPE_JOB;
    }

    public String[] getFilterNames() {
        return Const.getJobFilterNames();
    }

    public String[] getFilterExtensions() {
        return Const.STRING_JOB_FILTER_EXT;
    }

    public String getDefaultExtension() {
        return Const.STRING_JOB_DEFAULT_EXT;
    }

    public String getXML() {
        Props props = null;
        if (Props.isInitialized())
            props = Props.getInstance();

        DatabaseMeta ci = getLogConnection();
        StringBuffer retval = new StringBuffer(500);

        retval.append("<").append(XML_TAG).append(">").append(Const.CR); //$NON-NLS-1$

        retval.append("  ").append(XMLHandler.addTagValue("name", getName())); //$NON-NLS-1$ //$NON-NLS-2$

        retval.append("    ").append(XMLHandler.addTagValue("description", description)); //$NON-NLS-1$ //$NON-NLS-2$
        retval.append("    ").append(XMLHandler.addTagValue("extended_description", extendedDescription));
        retval.append("    ").append(XMLHandler.addTagValue("gui_location_x", guiLocationX)); //$NON-NLS-1$ //$NON-NLS-2$
        retval.append("    ").append(XMLHandler.addTagValue("gui_location_y", guiLocationY)); //$NON-NLS-1$ //$NON-NLS-2$
        retval.append("    ").append(XMLHandler.addTagValue("gui_scale", guiScale)); //$NON-NLS-1$ //$NON-NLS-2$
        retval.append("    ").append(XMLHandler.addTagValue("job_version", jobVersion));
        if (jobStatus >= 0) {
            retval.append("    ").append(XMLHandler.addTagValue("job_status", jobStatus));
        }

        retval.append("  ").append(XMLHandler.addTagValue("directory", directory.getPath())); //$NON-NLS-1$ //$NON-NLS-2$
        retval.append("  ").append(XMLHandler.addTagValue("created_user", created_user)); //$NON-NLS-1$ //$NON-NLS-2$
        retval.append("  ").append(XMLHandler.addTagValue("created_date", XMLHandler.date2string(created_date))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        retval.append("  ").append(XMLHandler.addTagValue("modified_user", modifiedUser)); //$NON-NLS-1$ //$NON-NLS-2$
        retval.append("  ").append(XMLHandler.addTagValue("modified_date", XMLHandler.date2string(created_date))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

        // Save the database connections...
        for (int i = 0; i < nrDatabases(); i++) {
            DatabaseMeta dbMeta = getDatabase(i);
            if (props != null && props.areOnlyUsedConnectionsSavedToXML()) {
                if (isDatabaseConnectionUsed(dbMeta)) {
                    retval.append(dbMeta.getXML());
                }
            } else {
                retval.append(dbMeta.getXML());
            }
        }

        // The slave servers...
        //
        retval.append("    ").append(XMLHandler.openTag(XML_TAG_SLAVESERVERS)).append(Const.CR); //$NON-NLS-1$
        for (int i = 0; i < slaveServers.size(); i++) {
            SlaveServer slaveServer = slaveServers.get(i);
            retval.append("         ").append(slaveServer.getXML()).append(Const.CR);
        }
        retval.append("    ").append(XMLHandler.closeTag(XML_TAG_SLAVESERVERS)).append(Const.CR); //$NON-NLS-1$

        retval.append("  ").append(XMLHandler.addTagValue("logconnection", ci == null ? "" : ci.getName())); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        retval.append("  ").append(XMLHandler.addTagValue("logtable", logTable)); //$NON-NLS-1$ //$NON-NLS-2$

        retval.append("   ").append(XMLHandler.addTagValue("use_batchid", useBatchId)); //$NON-NLS-1$ //$NON-NLS-2$
        retval.append("   ").append(XMLHandler.addTagValue("pass_batchid", batchIdPassed)); //$NON-NLS-1$ //$NON-NLS-2$
        retval.append("   ").append(XMLHandler.addTagValue("use_logfield", logfieldUsed)); //$NON-NLS-1$ //$NON-NLS-2$

        retval.append("   ").append(XMLHandler.addTagValue("shared_objects_file", sharedObjectsFile)); // $NON-NLS-1$

        retval.append("  <entries>").append(Const.CR); //$NON-NLS-1$
        for (int i = 0; i < nrJobEntries(); i++) {
            JobEntryCopy jge = getJobEntry(i);
            retval.append(jge.getXML());
        }
        retval.append("  </entries>").append(Const.CR); //$NON-NLS-1$

        retval.append("  <hops>").append(Const.CR); //$NON-NLS-1$
        for (JobHopMeta hi : jobhops) // Look at all the hops
        {
            retval.append(hi.getXML());
        }
        retval.append("  </hops>").append(Const.CR); //$NON-NLS-1$

        retval.append("  <notepads>").append(Const.CR); //$NON-NLS-1$
        for (int i = 0; i < nrNotes(); i++) {
            NotePadMeta ni = getNote(i);
            retval.append(ni.getXML());
        }
        retval.append("  </notepads>").append(Const.CR); //$NON-NLS-1$

        retval.append("</").append(XML_TAG).append(">").append(Const.CR); //$NON-NLS-1$

        return retval.toString();
    }

    public JobMeta(LogWriterInterface log, String fname, Repository rep) throws KettleXMLException {
        this(log, null, fname, rep, null);
    }

    public JobMeta(LogWriterInterface log, String fname, Repository rep, OverwritePrompter prompter)
            throws KettleXMLException {
        this(log, null, fname, rep, prompter);
    }

    /**
     * Load the job from the XML file specified.
     * 
     * @param log
     *            the logging channel
     * @param fname
     *            The filename to load as a job
     * @param rep
     *            The repository to bind againt, null if there is no repository
     *            available.
     * @throws KettleXMLException
     */
    public JobMeta(LogWriterInterface log, VariableSpace parentSpace, String fname, Repository rep,
            OverwritePrompter prompter) throws KettleXMLException {
        this.log = log;
        this.rep = rep;
        this.initializeVariablesFrom(parentSpace);
        try {
            // OK, try to load using the VFS stuff...
            Document doc = XMLHandler.loadXMLFile(KettleVFS.getFileObject(fname));
            if (doc != null) {
                // Clear the job
                clear();

                // The jobnode
                Node jobnode = XMLHandler.getSubNode(doc, XML_TAG);

                loadXML(jobnode, rep, prompter);

                // Do this at the end
                setFilename(fname);
            } else {
                throw new KettleXMLException(
                        Messages.getString("JobMeta.Exception.ErrorReadingFromXMLFile") + fname); //$NON-NLS-1$
            }
        } catch (Exception e) {
            throw new KettleXMLException(
                    Messages.getString("JobMeta.Exception.UnableToLoadJobFromXMLFile") + fname + "]", e); //$NON-NLS-1$ //$NON-NLS-2$
        }
    }

    public JobMeta(LogWriterInterface log, Node jobnode, Repository rep, OverwritePrompter prompter)
            throws KettleXMLException {
        this.log = log;
        this.rep = rep;
        loadXML(jobnode, rep, prompter);
    }

    public boolean isRepReference() {
        return isRepReference(getFilename(), this.getName());
    }

    public boolean isFileReference() {
        return !isRepReference(getFilename(), this.getName());
    }

    public static boolean isRepReference(String fileName, String transName) {
        return Const.isEmpty(fileName) && !Const.isEmpty(transName);
    }

    public static boolean isFileReference(String fileName, String transName) {
        return !isRepReference(fileName, transName);
    }

    public void loadXML(Node jobnode, Repository rep, OverwritePrompter prompter) throws KettleXMLException {
        Props props = null;
        if (Props.isInitialized())
            props = Props.getInstance();

        try {
            // clear the jobs;
            clear();

            //
            // get job info:
            //
            setName(XMLHandler.getTagValue(jobnode, "name")); //$NON-NLS-1$

            // Optionally load the repository directory...
            //
            if (rep != null) {
                String directoryPath = XMLHandler.getTagValue(jobnode, "directory");
                if (directoryPath != null) {
                    directory = rep.getDirectoryTree().findDirectory(directoryPath);
                    if (directory == null) { // not found
                        directory = new RepositoryDirectory(); // The root as
                        // default
                    }
                }
            }

            // description
            description = XMLHandler.getTagValue(jobnode, "description");

            // extended description
            extendedDescription = XMLHandler.getTagValue(jobnode, "extended_description");

            // gui location x
            //
            guiLocationX = Const.toInt(XMLHandler.getTagValue(jobnode, "gui_location_x"), 0);

            // gui location y
            //
            guiLocationY = Const.toInt(XMLHandler.getTagValue(jobnode, "gui_location_y"), 0);

            // gui scale
            //
            guiScale = Const.toDouble(XMLHandler.getTagValue(jobnode, "gui_scale"), 1);

            // job version
            jobVersion = XMLHandler.getTagValue(jobnode, "job_version");

            // job status
            jobStatus = Const.toInt(XMLHandler.getTagValue(jobnode, "job_status"), -1);

            // Created user/date
            created_user = XMLHandler.getTagValue(jobnode, "created_user"); //$NON-NLS-1$
            String createDate = XMLHandler.getTagValue(jobnode, "created_date"); //$NON-NLS-1$

            if (createDate != null) {
                created_date = XMLHandler.stringToDate(createDate);
            }

            // Changed user/date
            modifiedUser = XMLHandler.getTagValue(jobnode, "modified_user"); //$NON-NLS-1$
            String modDate = XMLHandler.getTagValue(jobnode, "modified_date"); //$NON-NLS-1$
            if (modDate != null) {
                modifiedDate = XMLHandler.stringToDate(modDate);
            }

            // Load the default list of databases
            // Read objects from the shared XML file & the repository
            try {
                sharedObjectsFile = XMLHandler.getTagValue(jobnode, "shared_objects_file"); //$NON-NLS-1$ //$NON-NLS-2$
                sharedObjects = readSharedObjects(rep);
            } catch (Exception e) {
                LogWriter.getInstance().logError(toString(),
                        Messages.getString("JobMeta.ErrorReadingSharedObjects.Message", e.toString())); // $NON-NLS-1$
                // //$NON-NLS-1$
                LogWriter.getInstance().logError(toString(), Const.getStackTracker(e));
            }

            // 
            // Read the database connections
            //
            int nr = XMLHandler.countNodes(jobnode, "connection"); //$NON-NLS-1$
            for (int i = 0; i < nr; i++) {
                Node dbnode = XMLHandler.getSubNodeByNr(jobnode, "connection", i); //$NON-NLS-1$
                DatabaseMeta dbcon = new DatabaseMeta(dbnode);
                dbcon.shareVariablesWith(this);

                DatabaseMeta exist = findDatabase(dbcon.getName());
                if (exist == null) {
                    addDatabase(dbcon);
                } else {
                    if (!exist.isShared()) // skip shared connections
                    {
                        boolean askOverwrite = Props.isInitialized() ? props.askAboutReplacingDatabaseConnections()
                                : false;
                        boolean overwrite = Props.isInitialized() ? props.replaceExistingDatabaseConnections()
                                : true;
                        if (askOverwrite && prompter != null) {
                            overwrite = prompter.overwritePrompt(
                                    Messages.getString("JobMeta.Dialog.ConnectionExistsOverWrite.Message",
                                            dbcon.getName()),
                                    Messages.getString(
                                            "JobMeta.Dialog.ConnectionExistsOverWrite.DontShowAnyMoreMessage"),
                                    Props.STRING_ASK_ABOUT_REPLACING_DATABASES);
                        }

                        if (overwrite) {
                            int idx = indexOfDatabase(exist);
                            removeDatabase(idx);
                            addDatabase(idx, dbcon);
                        }
                    }
                }
            }

            // Read the slave servers...
            // 
            Node slaveServersNode = XMLHandler.getSubNode(jobnode, XML_TAG_SLAVESERVERS); //$NON-NLS-1$
            int nrSlaveServers = XMLHandler.countNodes(slaveServersNode, SlaveServer.XML_TAG); //$NON-NLS-1$
            for (int i = 0; i < nrSlaveServers; i++) {
                Node slaveServerNode = XMLHandler.getSubNodeByNr(slaveServersNode, SlaveServer.XML_TAG, i);
                SlaveServer slaveServer = new SlaveServer(slaveServerNode);

                // Check if the object exists and if it's a shared object.
                // If so, then we will keep the shared version, not this one.
                // The stored XML is only for backup purposes.
                SlaveServer check = findSlaveServer(slaveServer.getName());
                if (check != null) {
                    if (!check.isShared()) // we don't overwrite shared
                    // objects.
                    {
                        addOrReplaceSlaveServer(slaveServer);
                    }
                } else {
                    slaveServers.add(slaveServer);
                }
            }

            /*
             * Get the log database connection & log table
             */
            String logcon = XMLHandler.getTagValue(jobnode, "logconnection"); //$NON-NLS-1$
            logConnection = findDatabase(logcon);
            logTable = XMLHandler.getTagValue(jobnode, "logtable"); //$NON-NLS-1$

            useBatchId = "Y".equalsIgnoreCase(XMLHandler.getTagValue(jobnode, "use_batchid")); //$NON-NLS-1$ //$NON-NLS-2$
            batchIdPassed = "Y".equalsIgnoreCase(XMLHandler.getTagValue(jobnode, "pass_batchid")); //$NON-NLS-1$ //$NON-NLS-2$
            logfieldUsed = "Y".equalsIgnoreCase(XMLHandler.getTagValue(jobnode, "use_logfield")); //$NON-NLS-1$ //$NON-NLS-2$

            /*
             * read the job entries...
             */
            Node entriesnode = XMLHandler.getSubNode(jobnode, "entries"); //$NON-NLS-1$
            int tr = XMLHandler.countNodes(entriesnode, "entry"); //$NON-NLS-1$
            for (int i = 0; i < tr; i++) {
                Node entrynode = XMLHandler.getSubNodeByNr(entriesnode, "entry", i); //$NON-NLS-1$
                // System.out.println("Reading entry:\n"+entrynode);

                JobEntryCopy je = new JobEntryCopy(entrynode, databases, slaveServers, rep);
                JobEntryCopy prev = findJobEntry(je.getName(), 0, true);
                if (prev != null) {
                    if (je.getNr() == 0) // See if the #0 already exists!
                    {
                        // Replace previous version with this one: remove it
                        // first
                        int idx = indexOfJobEntry(prev);
                        removeJobEntry(idx);
                    } else if (je.getNr() > 0) // Use previously defined
                    // JobEntry info!
                    {
                        je.setEntry(prev.getEntry());

                        // See if entry already exists...
                        prev = findJobEntry(je.getName(), je.getNr(), true);
                        if (prev != null) // remove the old one!
                        {
                            int idx = indexOfJobEntry(prev);
                            removeJobEntry(idx);
                        }
                    }
                }
                // Add the JobEntryCopy...
                addJobEntry(je);
            }

            Node hopsnode = XMLHandler.getSubNode(jobnode, "hops"); //$NON-NLS-1$
            int ho = XMLHandler.countNodes(hopsnode, "hop"); //$NON-NLS-1$
            for (int i = 0; i < ho; i++) {
                Node hopnode = XMLHandler.getSubNodeByNr(hopsnode, "hop", i); //$NON-NLS-1$
                JobHopMeta hi = new JobHopMeta(hopnode, this);
                jobhops.add(hi);
            }

            // Read the notes...
            Node notepadsnode = XMLHandler.getSubNode(jobnode, "notepads"); //$NON-NLS-1$
            int nrnotes = XMLHandler.countNodes(notepadsnode, "notepad"); //$NON-NLS-1$
            for (int i = 0; i < nrnotes; i++) {
                Node notepadnode = XMLHandler.getSubNodeByNr(notepadsnode, "notepad", i); //$NON-NLS-1$
                NotePadMeta ni = new NotePadMeta(notepadnode);
                notes.add(ni);
            }

            clearChanged();
        } catch (Exception e) {
            throw new KettleXMLException(Messages.getString("JobMeta.Exception.UnableToLoadJobFromXMLNode"), e); //$NON-NLS-1$
        } finally {
            setInternalKettleVariables();
        }
    }

    /**
     * Read the database connections in the repository and add them to this job
     * if they are not yet present.
     * 
     * @param rep
     *            The repository to load the database connections from.
     * @throws KettleException
     */
    public void readDatabases(Repository rep) throws KettleException {
        readDatabases(rep, true);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.panet.imeta.trans.HasDatabaseInterface#readDatabases(com.panet.imeta
     * .repository.Repository, boolean)
     */
    public void readDatabases(Repository rep, boolean overWriteShared) throws KettleException {
        try {
            long dbids[] = rep.getDatabaseIDs();
            for (int i = 0; i < dbids.length; i++) {
                DatabaseMeta databaseMeta = RepositoryUtil.loadDatabaseMeta(rep, dbids[i]);
                databaseMeta.shareVariablesWith(this);

                DatabaseMeta check = findDatabase(databaseMeta.getName()); // Check
                // if
                // there
                // already
                // is
                // one
                // in
                // the
                // transformation
                if (check == null || overWriteShared) // We only add, never
                // overwrite database
                // connections.
                {
                    if (databaseMeta.getName() != null) {
                        addOrReplaceDatabase(databaseMeta);
                        if (!overWriteShared)
                            databaseMeta.setChanged(false);
                    }
                }
            }
            setChanged(false);
        } catch (KettleDatabaseException dbe) {
            throw new KettleException(Messages.getString("JobMeta.Log.UnableToReadDatabaseIDSFromRepository"), dbe); //$NON-NLS-1$
        } catch (KettleException ke) {
            throw new KettleException(Messages.getString("JobMeta.Log.UnableToReadDatabasesFromRepository"), ke); //$NON-NLS-1$
        }
    }

    /**
     * Read the slave servers in the repository and add them to this
     * transformation if they are not yet present.
     * 
     * @param rep
     *            The repository to load from.
     * @param overWriteShared
     *            if an object with the same name exists, overwrite
     * @throws KettleException
     */
    public void readSlaves(Repository rep, boolean overWriteShared) throws KettleException {
        try {
            long dbids[] = rep.getSlaveIDs();
            for (int i = 0; i < dbids.length; i++) {
                SlaveServer slaveServer = new SlaveServer(rep, dbids[i]);
                SlaveServer check = findSlaveServer(slaveServer.getName()); // Check
                // if
                // there
                // already
                // is
                // one
                // in
                // the
                // transformation
                if (check == null || overWriteShared) {
                    if (!Const.isEmpty(slaveServer.getName())) {
                        addOrReplaceSlaveServer(slaveServer);
                        if (!overWriteShared)
                            slaveServer.setChanged(false);
                    }
                }
            }
        } catch (KettleDatabaseException dbe) {
            throw new KettleException(Messages.getString("JobMeta.Log.UnableToReadSlaveServersFromRepository"), //$NON-NLS-1$
                    dbe);
        }
    }

    public SharedObjects readSharedObjects(Repository rep) throws KettleException {
        // Extract the shared steps, connections, etc. using the SharedObjects
        // class
        //
        String soFile = environmentSubstitute(sharedObjectsFile);
        SharedObjects sharedObjects = new SharedObjects(soFile);
        Map<?, SharedObjectInterface> objectsMap = sharedObjects.getObjectsMap();

        // First read the databases...
        // We read databases & slaves first because there might be dependencies
        // that need to be resolved.
        //
        for (SharedObjectInterface object : objectsMap.values()) {
            if (object instanceof DatabaseMeta) {
                DatabaseMeta databaseMeta = (DatabaseMeta) object;
                addOrReplaceDatabase(databaseMeta);
            } else if (object instanceof SlaveServer) {
                SlaveServer slaveServer = (SlaveServer) object;
                addOrReplaceSlaveServer(slaveServer);
            }
        }

        if (rep != null) {
            readDatabases(rep, true);
            readSlaves(rep, true);
        }

        return sharedObjects;
    }

    public boolean saveSharedObjects() {
        try {
            // First load all the shared objects...
            String soFile = environmentSubstitute(sharedObjectsFile);
            SharedObjects sharedObjects = new SharedObjects(soFile);

            // Now overwrite the objects in there
            List<Object> shared = new ArrayList<Object>();
            shared.addAll(databases);
            shared.addAll(slaveServers);

            // The databases connections...
            for (int i = 0; i < shared.size(); i++) {
                SharedObjectInterface sharedObject = (SharedObjectInterface) shared.get(i);
                if (sharedObject.isShared()) {
                    sharedObjects.storeObject(sharedObject);
                }
            }

            // Save the objects
            sharedObjects.saveToFile();
            return true;
        } catch (Exception e) {
            log.logError(toString(), "Unable to save shared ojects: " + e.toString());
            return false;
        }
    }

    /**
     * Find a database connection by it's name
     * 
     * @param name
     *            The database name to look for
     * @return The database connection or null if nothing was found.
     */
    public DatabaseMeta findDatabase(String name) {
        for (int i = 0; i < nrDatabases(); i++) {
            DatabaseMeta ci = getDatabase(i);
            if (ci.getName().equalsIgnoreCase(name)) {
                return ci;
            }
        }
        return null;
    }

    public void saveRep(Repository rep) throws KettleException {
        saveRep(rep, null);
    }

    public void setInfo(Map<String, String[]> p, String id) throws KettleException {
        // 0 
        description = BaseStepMeta.parameterToString(p.get(id + ".description"));
        extendedDescription = BaseStepMeta.parameterToString(p.get(id + ".extendedDescription"));
        jobStatus = BaseStepMeta.parameterToInt(p.get(id + ".jobstatus"));
        jobVersion = BaseStepMeta.parameterToString(p.get(id + ".jobversion"));
        if (directory == null) {
            directory = new RepositoryDirectory();
        }
        directory = directory.findDirectory(BaseStepMeta.parameterToString(p.get(id + ".directory")));

        // 1 ?
        namedParams = new NamedParamsDefault();
        String[] params = p.get(id + "_parameters.parameter");
        String[] paramDescs = p.get(id + "_parameters.description");
        if (params != null)
            for (int i = 0; i < params.length; i++) {
                addParameterDefinition(params[i], paramDescs[i]);
            }

        // 2 
        int logConnectionId = BaseStepMeta.parameterToInt(p.get(id + ".logConnection"));
        if (logConnectionId > 0)
            logConnection = this.getDatabase(logConnectionId);
        else
            logConnection = null;

        logTable = BaseStepMeta.parameterToString(p.get(id + ".logTable"));

        useBatchId = BaseStepMeta.parameterToBoolean(p.get(id + ".batchIdUsed"));
        batchIdPassed = BaseStepMeta.parameterToBoolean(p.get(id + ".batchIdPassed"));
        logfieldUsed = BaseStepMeta.parameterToBoolean(p.get(id + ".logfieldUsed"));

        sharedObjectsFile = BaseStepMeta.parameterToString(p.get(id + ".sharedObjectsFile"));
    }

    public void saveRep(Repository rep, ProgressMonitorListener monitor) throws KettleException {
        try {
            int nrWorks = 2 + nrDatabases() + nrNotes() + nrJobEntries() + nrJobHops();
            if (monitor != null)
                monitor.beginTask(Messages.getString("JobMeta.Monitor.SavingTransformation") + directory //$NON-NLS-1$
                        + Const.FILE_SEPARATOR + getName(), nrWorks);

            // rep.lockRepository();

            rep.insertLogEntry("save job '" + getName() + "'"); //$NON-NLS-1$ //$NON-NLS-2$

            // Before we start, make sure we have a valid job ID!
            // Two possibilities:
            // 1) We have a ID: keep it
            // 2) We don't have an ID: look it up.
            // If we find a transformation with the same name: ask!
            //
            if (monitor != null)
                monitor.subTask(Messages.getString("JobMeta.Monitor.HandlingPreviousVersionOfJob")); //$NON-NLS-1$

            // If no valid id is available in the database, assign one...
            if (getID() <= 0) {
                setID(rep.getJobID(getName(), directory.getID()));
                if (getID() <= 0) {
                    setID(rep.getNextJobID());
                }
            } else {
                // If we have a valid ID, we need to make sure everything is
                // cleared out
                // of the database for this id_job, before we put it back in...
                rep.delAllFromJob(getID());
            }
            if (monitor != null)
                monitor.worked(1);

            // First of all we need to verify that all database connections are
            // saved.
            //
            if (log.isDebug())
                log.logDebug(toString(), Messages.getString("JobMeta.Log.SavingDatabaseConnections")); //$NON-NLS-1$
            for (int i = 0; i < nrDatabases(); i++) {
                if (monitor != null)
                    monitor.subTask(Messages.getString("JobMeta.Monitor.SavingDatabaseTask.Title") + (i + 1) + "/" //$NON-NLS-1$//$NON-NLS-2$
                            + nrDatabases());
                DatabaseMeta databaseMeta = getDatabase(i);
                // ONLY save the database connection if it has changed and
                // nothing was saved in the repository
                if (databaseMeta.hasChanged() || databaseMeta.getID() <= 0) {
                    RepositoryUtil.saveDatabaseMeta(databaseMeta, rep);
                }
                if (monitor != null)
                    monitor.worked(1);
            }

            // Now, save the job entry in R_JOB
            // Note, we save this first so that we have an ID in the database.
            // Everything else depends on this ID, including recursive job
            // entries to the save job. (retry)
            if (monitor != null)
                monitor.subTask(Messages.getString("JobMeta.Monitor.SavingJobDetails")); //$NON-NLS-1$
            if (log.isDetailed())
                log.logDetailed(toString(), "Saving job info to repository..."); //$NON-NLS-1$
            saveRepJob(rep);
            if (monitor != null)
                monitor.worked(1);

            // Save the slaves
            //
            for (int i = 0; i < slaveServers.size(); i++) {
                SlaveServer slaveServer = slaveServers.get(i);
                slaveServer.saveRep(rep, getID(), false);
            }

            //
            // Save the notes
            //
            if (log.isDetailed())
                log.logDetailed(toString(), "Saving notes to repository..."); //$NON-NLS-1$
            for (int i = 0; i < nrNotes(); i++) {
                if (monitor != null)
                    monitor.subTask(Messages.getString("JobMeta.Monitor.SavingNoteNr") + (i + 1) + "/" + nrNotes()); //$NON-NLS-1$ //$NON-NLS-2$
                NotePadMeta ni = getNote(i);
                ni.saveRep(rep, getID());
                if (ni.getID() > 0) {
                    rep.insertJobNote(getID(), ni.getID());
                }
                if (monitor != null)
                    monitor.worked(1);
            }

            //
            // Save the job entries
            //
            if (log.isDetailed())
                log.logDetailed(toString(), "Saving " + nrJobEntries() + " Job enty copies to repository..."); //$NON-NLS-1$ //$NON-NLS-2$
            rep.updateJobEntryTypes();
            for (int i = 0; i < nrJobEntries(); i++) {
                if (monitor != null)
                    monitor.subTask(Messages.getString("JobMeta.Monitor.SavingJobEntryNr") + (i + 1) + "/" //$NON-NLS-1$//$NON-NLS-2$
                            + nrJobEntries());
                JobEntryCopy cge = getJobEntry(i);
                cge.saveRep(rep, getID());
                if (monitor != null)
                    monitor.worked(1);
            }

            if (log.isDetailed())
                log.logDetailed(toString(), "Saving job hops to repository..."); //$NON-NLS-1$
            for (int i = 0; i < nrJobHops(); i++) {
                if (monitor != null)
                    monitor.subTask("Saving job hop #" + (i + 1) + "/" + nrJobHops()); //$NON-NLS-1$ //$NON-NLS-2$
                JobHopMeta hi = getJobHop(i);
                hi.saveRep(rep, getID());
                if (monitor != null)
                    monitor.worked(1);
            }

            // Commit this transaction!!
            rep.commit();

            clearChanged();
            if (monitor != null)
                monitor.done();
        } catch (KettleDatabaseException dbe) {
            rep.rollback();
            throw new KettleException(
                    Messages.getString("JobMeta.Exception.UnableToSaveJobInRepositoryRollbackPerformed"), dbe); //$NON-NLS-1$
        } finally {
            // don't forget to unlock the repository.
            // Normally this is done by the commit / rollback statement, but hey
            // there are some freaky database out
            // there...
            rep.unlockRepository();
        }

    }

    /**
     * Load a job in a directory
     * 
     * @param log
     *            the logging channel
     * @param rep
     *            The Repository
     * @param jobname
     *            The name of the job
     * @param repdir
     *            The directory in which the job resides.
     * @throws KettleException
     */
    public JobMeta(LogWriterInterface log, Repository rep, String jobname, RepositoryDirectory repdir)
            throws KettleException {
        this(log, rep, jobname, repdir, null);
    }

    /**
     * Load a job in a directory
     * 
     * @param log
     *            the logging channel
     * @param rep
     *            The Repository
     * @param jobname
     *            The name of the job
     * @param repdir
     *            The directory in which the job resides.
     * @throws KettleException
     */
    public JobMeta(LogWriterInterface log, Repository rep, String jobname, RepositoryDirectory repdir,
            ProgressMonitorListener monitor) throws KettleException {
        this.log = log;
        this.rep = rep;
        synchronized (rep) {
            try {
                // Clear everything...
                clear();

                directory = repdir;

                // Get the transformation id
                setID(rep.getJobID(jobname, repdir.getID()));

                // If no valid id is available in the database, then give
                // error...
                if (getID() > 0) {
                    // Load the notes...
                    long noteids[] = rep.getJobNoteIDs(getID());
                    long jecids[] = rep.getJobEntryCopyIDs(getID());
                    long hopid[] = rep.getJobHopIDs(getID());

                    int nrWork = 2 + noteids.length + jecids.length + hopid.length;
                    if (monitor != null)
                        monitor.beginTask(Messages.getString("JobMeta.Monitor.LoadingJob") + repdir //$NON-NLS-1$
                                + Const.FILE_SEPARATOR + jobname, nrWork);

                    //
                    // get job info:
                    //
                    if (monitor != null)
                        monitor.subTask(Messages.getString("JobMeta.Monitor.ReadingJobInformation")); //$NON-NLS-1$
                    RowMetaAndData jobRow = rep.getJob(getID());

                    setName(jobRow.getString("NAME", null)); //$NON-NLS-1$
                    description = jobRow.getString("DESCRIPTION", null); //$NON-NLS-1$
                    extendedDescription = jobRow.getString("EXTENDED_DESCRIPTION", null); //$NON-NLS-1$

                    guiLocationX = (int) jobRow.getInteger("GUI_LOCATION_X", 0L);
                    guiLocationY = (int) jobRow.getInteger("GUI_LOCATION_Y", 0L);
                    guiScale = jobRow.getNumber("GUI_SCALE", 1);

                    jobVersion = jobRow.getString("JOB_VERSION", null); //$NON-NLS-1$
                    jobStatus = Const.toInt(jobRow.getString("JOB_STATUS", null), -1); //$NON-NLS-1$
                    logTable = jobRow.getString("TABLE_NAME_LOG", null); //$NON-NLS-1$

                    created_user = jobRow.getString("CREATED_USER", null); //$NON-NLS-1$
                    created_date = jobRow.getDate("CREATED_DATE", new Date()); //$NON-NLS-1$

                    modifiedUser = jobRow.getString("MODIFIED_USER", null); //$NON-NLS-1$
                    modifiedDate = jobRow.getDate("MODIFIED_DATE", new Date()); //$NON-NLS-1$

                    long id_logdb = jobRow.getInteger("ID_DATABASE_LOG", 0); //$NON-NLS-1$
                    if (id_logdb > 0) {
                        // Get the logconnection
                        logConnection = RepositoryUtil.loadDatabaseMeta(rep, id_logdb);
                        logConnection.shareVariablesWith(this);
                    }
                    useBatchId = jobRow.getBoolean("USE_BATCH_ID", false); //$NON-NLS-1$
                    batchIdPassed = jobRow.getBoolean("PASS_BATCH_ID", false); //$NON-NLS-1$
                    logfieldUsed = jobRow.getBoolean("USE_LOGFIELD", false); //$NON-NLS-1$

                    if (monitor != null)
                        monitor.worked(1);
                    // 
                    // Load the common database connections
                    //
                    if (monitor != null)
                        monitor.subTask(
                                Messages.getString("JobMeta.Monitor.ReadingAvailableDatabasesFromRepository")); //$NON-NLS-1$
                    // Read objects from the shared XML file & the repository
                    try {
                        sharedObjectsFile = jobRow.getString("SHARED_FILE", null);
                        sharedObjects = readSharedObjects(rep);
                    } catch (Exception e) {
                        LogWriter.getInstance().logError(toString(),
                                Messages.getString("JobMeta.ErrorReadingSharedObjects.Message", e.toString())); // $NON-NLS-1$
                        // //$NON-NLS-1$
                        LogWriter.getInstance().logError(toString(), Const.getStackTracker(e));
                    }
                    if (monitor != null)
                        monitor.worked(1);

                    if (log.isDetailed())
                        log.logDetailed(toString(), "Loading " + noteids.length + " notes"); //$NON-NLS-1$ //$NON-NLS-2$
                    for (int i = 0; i < noteids.length; i++) {
                        if (monitor != null)
                            monitor.subTask(Messages.getString("JobMeta.Monitor.ReadingNoteNr") + (i + 1) + "/" //$NON-NLS-1$//$NON-NLS-2$
                                    + noteids.length);
                        NotePadMeta ni = new NotePadMeta(log, rep, noteids[i]);
                        if (indexOfNote(ni) < 0)
                            addNote(ni);
                        if (monitor != null)
                            monitor.worked(1);
                    }

                    // Load the job entries...
                    if (log.isDetailed())
                        log.logDetailed(toString(), "Loading " + jecids.length + " job entries"); //$NON-NLS-1$ //$NON-NLS-2$
                    for (int i = 0; i < jecids.length; i++) {
                        if (monitor != null)
                            monitor.subTask(Messages.getString("JobMeta.Monitor.ReadingJobEntryNr") + (i + 1) + "/" //$NON-NLS-1$//$NON-NLS-2$
                                    + (jecids.length));

                        JobEntryCopy jec = new JobEntryCopy(log, rep, getID(), jecids[i], jobentries, databases,
                                slaveServers);
                        // Also set the copy number...
                        // We count the number of job entry copies that use the
                        // job
                        // entry
                        //
                        int copyNr = 0;
                        for (JobEntryCopy copy : jobcopies) {
                            if (jec.getEntry() == copy.getEntry()) {
                                copyNr++;
                            }
                        }
                        jec.setNr(copyNr);

                        int idx = indexOfJobEntry(jec);
                        if (idx < 0) {
                            if (jec.getName() != null && jec.getName().length() > 0)
                                addJobEntry(jec);
                        } else {
                            setJobEntry(idx, jec); // replace it!
                        }
                        if (monitor != null)
                            monitor.worked(1);
                    }

                    // Load the hops...
                    if (log.isDetailed())
                        log.logDetailed(toString(), "Loading " + hopid.length + " job hops"); //$NON-NLS-1$ //$NON-NLS-2$
                    for (int i = 0; i < hopid.length; i++) {
                        if (monitor != null)
                            monitor.subTask(Messages.getString("JobMeta.Monitor.ReadingJobHopNr") + (i + 1) + "/" //$NON-NLS-1$//$NON-NLS-2$
                                    + (jecids.length));
                        JobHopMeta hi = new JobHopMeta(rep, hopid[i], this, jobcopies);
                        jobhops.add(hi);
                        if (monitor != null)
                            monitor.worked(1);
                    }

                    // Finally, clear the changed flags...
                    clearChanged();
                    if (monitor != null)
                        monitor.subTask(Messages.getString("JobMeta.Monitor.FinishedLoadOfJob")); //$NON-NLS-1$
                    if (monitor != null)
                        monitor.done();
                } else {
                    throw new KettleException(Messages.getString("JobMeta.Exception.CanNotFindJob") + jobname); //$NON-NLS-1$
                }
            } catch (KettleException dbe) {
                throw new KettleException(Messages.getString("JobMeta.Exception.AnErrorOccuredReadingJob", jobname),
                        dbe);
            } finally {
                setInternalKettleVariables();
            }
        }
    }

    public JobEntryCopy getJobEntryCopy(int x, int y, int iconsize) {
        int i, s;
        s = nrJobEntries();
        for (i = s - 1; i >= 0; i--) // Back to front because drawing goes
        // from start to end
        {
            JobEntryCopy je = getJobEntry(i);
            Point p = je.getLocation();
            if (p != null) {
                if (x >= p.x && x <= p.x + iconsize && y >= p.y && y <= p.y + iconsize) {
                    return je;
                }
            }
        }
        return null;
    }

    public int nrJobEntries() {
        return jobcopies.size();
    }

    public int nrJobHops() {
        return jobhops.size();
    }

    public int nrNotes() {
        return notes.size();
    }

    public int nrDatabases() {
        return databases.size();
    }

    public JobHopMeta getJobHop(int i) {
        return jobhops.get(i);
    }

    public List<JobHopMeta> getJobHops() {
        return jobhops;
    }

    public JobEntryCopy getJobEntry(int i) {
        return jobcopies.get(i);
    }

    public JobEntryCopy getJobEntry(String n) {
        for (int i = 0; i < jobcopies.size(); i++) {
            if (jobcopies.get(i).getName().equals(n)) {
                return jobcopies.get(i);
            }
        }
        return null;
    }

    public JobEntryCopy getJobEntryByGraphId(String graphId) {
        if (StringUtils.isNotEmpty(graphId)) {
            JobEntryCopy jec;
            for (int i = 0; i < jobcopies.size(); i++) {
                jec = jobcopies.get(i);
                if (graphId.equals(jec.getGraphId())) {
                    return jec;
                }
            }
        }
        return null;
    }

    public JobHopMeta getJobHopByGraphId(String graphId) {
        if (StringUtils.isNotEmpty(graphId)) {
            JobHopMeta jhm;
            for (int i = 0; i < jobhops.size(); i++) {
                jhm = jobhops.get(i);
                if (graphId.equals(jhm.getGraphId())) {
                    return jhm;
                }
            }
        }
        return null;
    }

    public int getJobEntryIndex(String n) {
        for (int i = 0; i < jobcopies.size(); i++) {
            if (jobcopies.get(i).getName().equals(n)) {
                return i;
            }
        }
        return -1;
    }

    public List<JobEntryCopy> getJobEntries() {
        return jobcopies;
    }

    public NotePadMeta getNote(int i) {
        return notes.get(i);
    }

    public DatabaseMeta getDatabase(int i) {
        return databases.get(i);
    }

    public void addJobEntry(JobEntryCopy je) {
        jobcopies.add(je);
        setChanged();
    }

    public void addJobHop(JobHopMeta hi) {
        jobhops.add(hi);
        setChanged();
    }

    public void addNote(NotePadMeta ni) {
        notes.add(ni);
        setChanged();
    }

    public void addDatabase(DatabaseMeta ci) {
        databases.add(ci);
        Collections.sort(databases, DatabaseMeta.comparator);
        changedDatabases = true;
    }

    public void addJobEntry(int p, JobEntryCopy si) {
        jobcopies.add(p, si);
        changedEntries = true;
    }

    public void addJobHop(int p, JobHopMeta hi) {
        jobhops.add(p, hi);
        changedHops = true;
    }

    public void addNote(int p, NotePadMeta ni) {
        notes.add(p, ni);
        changedNotes = true;
    }

    public void addDatabase(int p, DatabaseMeta ci) {
        databases.add(p, ci);
        changedDatabases = true;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.panet.imeta.trans.HasDatabaseInterface#addOrReplaceDatabase(com.panet
     * .imeta.core.database.DatabaseMeta)
     */
    public void addOrReplaceDatabase(DatabaseMeta databaseMeta) {
        int index = databases.indexOf(databaseMeta);
        if (index < 0) {
            addDatabase(databaseMeta);
        } else {
            DatabaseMeta previous = getDatabase(index);
            previous.replaceMeta(databaseMeta);
        }
        changedDatabases = true;
    }

    /**
     * Add a new slave server to the transformation if that didn't exist yet.
     * Otherwise, replace it.
     * 
     * @param slaveServer
     *            The slave server to be added.
     */
    public void addOrReplaceSlaveServer(SlaveServer slaveServer) {
        int index = slaveServers.indexOf(slaveServer);
        if (index < 0) {
            slaveServers.add(slaveServer);
        } else {
            SlaveServer previous = slaveServers.get(index);
            previous.replaceMeta(slaveServer);
        }
        setChanged();
    }

    public void removeJobEntry(int i) {
        jobcopies.remove(i);
        setChanged();
    }

    public void removeAllJobEntries() {
        int len = jobcopies.size();
        for (int i = 0; i < len; i++) {
            jobcopies.remove(0);
        }
        setChanged();
    }

    public void removeJobHop(int i) {
        jobhops.remove(i);
        setChanged();
    }

    public void removeAllJobHops() {
        int len = jobhops.size();
        for (int i = 0; i < len; i++) {
            jobhops.remove(0);
        }
        setChanged();
    }

    public void removeNote(int i) {
        notes.remove(i);
        setChanged();
    }

    public void raiseNote(int p) {
        // if valid index and not last index
        if ((p >= 0) && (p < notes.size() - 1)) {
            NotePadMeta note = notes.remove(p);
            notes.add(note);
            changedNotes = true;
        }
    }

    public void lowerNote(int p) {
        // if valid index and not first index
        if ((p > 0) && (p < notes.size())) {
            NotePadMeta note = notes.remove(p);
            notes.add(0, note);
            changedNotes = true;
        }
    }

    public void removeDatabase(int i) {
        if (i < 0 || i >= databases.size())
            return;
        databases.remove(i);
        changedDatabases = true;
    }

    public void removeDatabase(long id) {
        DatabaseMeta d;
        for (int i = 0; i < databases.size(); i++) {
            d = databases.get(i);
            if (d.getID() == id) {
                removeDatabase(i);
                return;
            }
        }
    }

    public int indexOfJobHop(JobHopMeta he) {
        return jobhops.indexOf(he);
    }

    public int indexOfNote(NotePadMeta ni) {
        return notes.indexOf(ni);
    }

    public int indexOfJobEntry(JobEntryCopy ge) {
        return jobcopies.indexOf(ge);
    }

    public int indexOfDatabase(DatabaseMeta di) {
        return databases.indexOf(di);
    }

    public void setJobEntry(int idx, JobEntryCopy jec) {
        jobcopies.set(idx, jec);
    }

    /**
     * Find an existing JobEntryCopy by it's name and number
     * 
     * @param name
     *            The name of the job entry copy
     * @param nr
     *            The number of the job entry copy
     * @return The JobEntryCopy or null if nothing was found!
     */
    public JobEntryCopy findJobEntry(String name, int nr, boolean searchHiddenToo) {
        for (int i = 0; i < nrJobEntries(); i++) {
            JobEntryCopy jec = getJobEntry(i);
            if (jec.getTypeDesc().equalsIgnoreCase(name) && jec.getNr() == nr) {
                if (searchHiddenToo || jec.isDrawn()) {
                    return jec;
                }
            }
        }
        return null;
    }

    public JobEntryCopy findJobEntry(String full_name_nr) {
        if (full_name_nr == null)
            return null;
        int i;
        for (i = 0; i < nrJobEntries(); i++) {
            JobEntryCopy jec = getJobEntry(i);
            JobEntryInterface je = jec.getEntry();

            if (je.toString().equalsIgnoreCase(full_name_nr)) {
                return jec;
            }
        }
        return null;
    }

    public JobHopMeta findJobHop(String name) {
        for (JobHopMeta hi : jobhops) // Look at all the hops
        {
            if (hi.toString().equalsIgnoreCase(name)) {
                return hi;
            }
        }
        return null;
    }

    public JobHopMeta findJobHopFrom(JobEntryCopy jge) {
        if (jge != null) {
            for (JobHopMeta hi : jobhops) {
                if (hi != null && (hi.from_entry != null) && hi.from_entry.equals(jge)) // return
                // the
                // first
                {
                    return hi;
                }
            }
        }
        return null;
    }

    public JobHopMeta findJobHop(JobEntryCopy from, JobEntryCopy to) {
        for (JobHopMeta hi : jobhops) {
            if (hi.isEnabled()) {
                if (hi != null && hi.from_entry != null && hi.to_entry != null && hi.from_entry.equals(from)
                        && hi.to_entry.equals(to)) {
                    return hi;
                }
            }
        }
        return null;
    }

    public JobHopMeta findJobHopTo(JobEntryCopy jge) {
        for (JobHopMeta hi : jobhops) {
            if (hi != null && hi.to_entry != null && hi.to_entry.equals(jge)) // Return
            // the
            // first!
            {
                return hi;
            }
        }
        return null;
    }

    public int findNrPrevJobEntries(JobEntryCopy from) {
        return findNrPrevJobEntries(from, false);
    }

    public JobEntryCopy findPrevJobEntry(JobEntryCopy to, int nr) {
        return findPrevJobEntry(to, nr, false);
    }

    public int findNrPrevJobEntries(JobEntryCopy to, boolean info) {
        int count = 0;

        for (JobHopMeta hi : jobhops) // Look at all the hops
        {
            if (hi.isEnabled() && hi.to_entry.equals(to)) {
                count++;
            }
        }
        return count;
    }

    public JobEntryCopy findPrevJobEntry(JobEntryCopy to, int nr, boolean info) {
        int count = 0;

        for (JobHopMeta hi : jobhops) // Look at all the hops
        {
            if (hi.isEnabled() && hi.to_entry.equals(to)) {
                if (count == nr) {
                    return hi.from_entry;
                }
                count++;
            }
        }
        return null;
    }

    public int findNrNextJobEntries(JobEntryCopy from) {
        int count = 0;
        for (JobHopMeta hi : jobhops) // Look at all the hops
        {
            if (hi.isEnabled() && (hi.from_entry != null) && hi.from_entry.equals(from))
                count++;
        }
        return count;
    }

    public JobEntryCopy findNextJobEntry(JobEntryCopy from, int cnt) {
        int count = 0;

        for (JobHopMeta hi : jobhops) // Look at all the hops
        {
            if (hi.isEnabled() && (hi.from_entry != null) && hi.from_entry.equals(from)) {
                if (count == cnt) {
                    return hi.to_entry;
                }
                count++;
            }
        }
        return null;
    }

    public boolean hasLoop(JobEntryCopy entry) {
        return hasLoop(entry, null);
    }

    public boolean hasLoop(JobEntryCopy entry, JobEntryCopy lookup) {
        return false;
    }

    public boolean isEntryUsedInHops(JobEntryCopy jge) {
        JobHopMeta fr = findJobHopFrom(jge);
        JobHopMeta to = findJobHopTo(jge);
        if (fr != null || to != null)
            return true;
        return false;
    }

    public int countEntries(String name) {
        int count = 0;
        int i;
        for (i = 0; i < nrJobEntries(); i++) // Look at all the hops;
        {
            JobEntryCopy je = getJobEntry(i);
            if (je.getName().equalsIgnoreCase(name))
                count++;
        }
        return count;
    }

    public int generateJobEntryNameNr(String basename) {
        int nr = 1;

        JobEntryCopy e = findJobEntry(basename + " " + nr, 0, true); //$NON-NLS-1$
        while (e != null) {
            nr++;
            e = findJobEntry(basename + " " + nr, 0, true); //$NON-NLS-1$
        }
        return nr;
    }

    public int findUnusedNr(String name) {
        int nr = 1;
        JobEntryCopy je = findJobEntry(name, nr, true);
        while (je != null) {
            nr++;
            // log.logDebug("findUnusedNr()", "Trying unused nr: "+nr);
            je = findJobEntry(name, nr, true);
        }
        return nr;
    }

    public int findMaxNr(String name) {
        int max = 0;
        for (int i = 0; i < nrJobEntries(); i++) {
            JobEntryCopy je = getJobEntry(i);
            if (je.getName().equalsIgnoreCase(name)) {
                if (je.getNr() > max)
                    max = je.getNr();
            }
        }
        return max;
    }

    /**
     * Proposes an alternative job entry name when the original already
     * exists...
     * 
     * @param entryname
     *            The job entry name to find an alternative for..
     * @return The alternative stepname.
     */
    public String getAlternativeJobentryName(String entryname) {
        String newname = entryname;
        JobEntryCopy jec = findJobEntry(newname);
        int nr = 1;
        while (jec != null) {
            nr++;
            newname = entryname + " " + nr; //$NON-NLS-1$
            jec = findJobEntry(newname);
        }

        return newname;
    }

    public JobEntryCopy[] getAllJobGraphEntries(String name) {
        int count = 0;
        for (int i = 0; i < nrJobEntries(); i++) {
            JobEntryCopy je = getJobEntry(i);
            if (je.getName().equalsIgnoreCase(name))
                count++;
        }
        JobEntryCopy retval[] = new JobEntryCopy[count];

        count = 0;
        for (int i = 0; i < nrJobEntries(); i++) {
            JobEntryCopy je = getJobEntry(i);
            if (je.getName().equalsIgnoreCase(name)) {
                retval[count] = je;
                count++;
            }
        }
        return retval;
    }

    public JobHopMeta[] getAllJobHopsUsing(String name) {
        List<JobHopMeta> hops = new ArrayList<JobHopMeta>();

        for (JobHopMeta hi : jobhops) // Look at all the hops
        {
            if (hi.from_entry != null && hi.to_entry != null) {
                if (hi.from_entry.getName().equalsIgnoreCase(name)
                        || hi.to_entry.getName().equalsIgnoreCase(name)) {
                    hops.add(hi);
                }
            }
        }
        return hops.toArray(new JobHopMeta[hops.size()]);
    }

    public NotePadMeta getNote(int x, int y) {
        int i, s;
        s = notes.size();
        for (i = s - 1; i >= 0; i--) // Back to front because drawing goes
        // from start to end
        {
            NotePadMeta ni = notes.get(i);
            Point loc = ni.getLocation();
            Point p = new Point(loc.x, loc.y);
            if (x >= p.x && x <= p.x + ni.width + 2 * Const.NOTE_MARGIN && y >= p.y
                    && y <= p.y + ni.height + 2 * Const.NOTE_MARGIN) {
                return ni;
            }
        }
        return null;
    }

    public void selectAll() {
        int i;
        for (i = 0; i < nrJobEntries(); i++) {
            JobEntryCopy ce = getJobEntry(i);
            ce.setSelected(true);
        }

        setChanged();
        notifyObservers("refreshGraph");
    }

    public void unselectAll() {
        int i;
        for (i = 0; i < nrJobEntries(); i++) {
            JobEntryCopy ce = getJobEntry(i);
            ce.setSelected(false);
        }
    }

    public int getMaxUndo() {
        return max_undo;
    }

    public void setMaxUndo(int mu) {
        max_undo = mu;
        while (undo.size() > mu && undo.size() > 0)
            undo.remove(0);
    }

    public int getUndoSize() {
        if (undo == null)
            return 0;
        return undo.size();
    }

    public void clearUndo() {
        undo = new ArrayList<TransAction>();
        undo_position = -1;
    }

    public void addUndo(Object from[], Object to[], int pos[], Point prev[], Point curr[], int type_of_change,
            boolean nextAlso) {
        // First clean up after the current position.
        // Example: position at 3, size=5
        // 012345
        // ^
        // remove 34
        // Add 4
        // 01234

        while (undo.size() > undo_position + 1 && undo.size() > 0) {
            int last = undo.size() - 1;
            undo.remove(last);
        }

        TransAction ta = new TransAction();
        switch (type_of_change) {
        case TYPE_UNDO_CHANGE:
            ta.setChanged(from, to, pos);
            break;
        case TYPE_UNDO_DELETE:
            ta.setDelete(from, pos);
            break;
        case TYPE_UNDO_NEW:
            ta.setNew(from, pos);
            break;
        case TYPE_UNDO_POSITION:
            ta.setPosition(from, pos, prev, curr);
            break;
        }
        undo.add(ta);
        undo_position++;

        if (undo.size() > max_undo) {
            undo.remove(0);
            undo_position--;
        }
    }

    // get previous undo, change position
    public TransAction previousUndo() {
        if (undo.isEmpty() || undo_position < 0)
            return null; // No undo left!

        TransAction retval = undo.get(undo_position);

        undo_position--;

        return retval;
    }

    /**
     * View current undo, don't change undo position
     * 
     * @return The current undo transaction
     */
    public TransAction viewThisUndo() {
        if (undo.isEmpty() || undo_position < 0)
            return null; // No undo left!

        TransAction retval = undo.get(undo_position);

        return retval;
    }

    // View previous undo, don't change position
    public TransAction viewPreviousUndo() {
        if (undo.isEmpty() || undo_position < 0)
            return null; // No undo left!

        TransAction retval = undo.get(undo_position);

        return retval;
    }

    public TransAction nextUndo() {
        int size = undo.size();
        if (size == 0 || undo_position >= size - 1)
            return null; // no redo left...

        undo_position++;

        TransAction retval = undo.get(undo_position);

        return retval;
    }

    public TransAction viewNextUndo() {
        int size = undo.size();
        if (size == 0 || undo_position >= size - 1)
            return null; // no redo left...

        TransAction retval = undo.get(undo_position + 1);

        return retval;
    }

    public Point getMaximum() {
        int maxx = 0, maxy = 0;
        for (int i = 0; i < nrJobEntries(); i++) {
            JobEntryCopy entry = getJobEntry(i);
            Point loc = entry.getLocation();
            if (loc.x > maxx)
                maxx = loc.x;
            if (loc.y > maxy)
                maxy = loc.y;
        }
        for (int i = 0; i < nrNotes(); i++) {
            NotePadMeta ni = getNote(i);
            Point loc = ni.getLocation();
            if (loc.x + ni.width > maxx)
                maxx = loc.x + ni.width;
            if (loc.y + ni.height > maxy)
                maxy = loc.y + ni.height;
        }

        return new Point(maxx + 100, maxy + 100);
    }

    public Point[] getSelectedLocations() {
        int sels = nrSelected();
        Point retval[] = new Point[sels];
        for (int i = 0; i < sels; i++) {
            JobEntryCopy si = getSelected(i);
            Point p = si.getLocation();
            retval[i] = new Point(p.x, p.y); // explicit copy of location
        }
        return retval;
    }

    public JobEntryCopy[] getSelectedEntries() {
        int sels = nrSelected();
        if (sels == 0)
            return null;

        JobEntryCopy retval[] = new JobEntryCopy[sels];
        for (int i = 0; i < sels; i++) {
            JobEntryCopy je = getSelected(i);
            retval[i] = je;
        }
        return retval;
    }

    public int nrSelected() {
        int i, count;
        count = 0;
        for (i = 0; i < nrJobEntries(); i++) {
            JobEntryCopy je = getJobEntry(i);
            if (je.isSelected() && je.isDrawn())
                count++;
        }
        return count;
    }

    public JobEntryCopy getSelected(int nr) {
        int i, count;
        count = 0;
        for (i = 0; i < nrJobEntries(); i++) {
            JobEntryCopy je = getJobEntry(i);
            if (je.isSelected()) {
                if (nr == count)
                    return je;
                count++;
            }
        }
        return null;
    }

    public int[] getEntryIndexes(JobEntryCopy entries[]) {
        int retval[] = new int[entries.length];

        for (int i = 0; i < entries.length; i++)
            retval[i] = indexOfJobEntry(entries[i]);

        return retval;
    }

    public JobEntryCopy findStart() {
        for (int i = 0; i < nrJobEntries(); i++) {
            if (getJobEntry(i).isStart())
                return getJobEntry(i);
        }
        return null;
    }

    public String toString() {
        if (name != null)
            return name;
        if (filename != null)
            return filename;
        else
            return getClass().getName();
    }

    /**
     * @return Returns the logfieldUsed.
     */
    public boolean isLogfieldUsed() {
        return logfieldUsed;
    }

    /**
     * @param logfieldUsed
     *            The logfieldUsed to set.
     */
    public void setLogfieldUsed(boolean logfieldUsed) {
        this.logfieldUsed = logfieldUsed;
    }

    /**
     * @return Returns the useBatchId.
     */
    public boolean isBatchIdUsed() {
        return useBatchId;
    }

    /**
     * @param useBatchId
     *            The useBatchId to set.
     */
    public void setUseBatchId(boolean useBatchId) {
        this.useBatchId = useBatchId;
    }

    /**
     * @return Returns the batchIdPassed.
     */
    public boolean isBatchIdPassed() {
        return batchIdPassed;
    }

    /**
     * @param batchIdPassed
     *            The batchIdPassed to set.
     */
    public void setBatchIdPassed(boolean batchIdPassed) {
        this.batchIdPassed = batchIdPassed;
    }

    /**
     * Builds a list of all the SQL statements that this transformation needs in
     * order to work properly.
     * 
     * @return An ArrayList of SQLStatement objects.
     */
    public List<SQLStatement> getSQLStatements(Repository repository, ProgressMonitorListener monitor)
            throws KettleException {
        if (monitor != null)
            monitor.beginTask(Messages.getString("JobMeta.Monitor.GettingSQLNeededForThisJob"), nrJobEntries() + 1); //$NON-NLS-1$
        List<SQLStatement> stats = new ArrayList<SQLStatement>();

        for (int i = 0; i < nrJobEntries(); i++) {
            JobEntryCopy copy = getJobEntry(i);
            if (monitor != null)
                monitor.subTask(Messages.getString("JobMeta.Monitor.GettingSQLForJobEntryCopy") + copy + "]"); //$NON-NLS-1$ //$NON-NLS-2$
            List<SQLStatement> list = copy.getEntry().getSQLStatements(repository, this);
            stats.addAll(list);
            if (monitor != null)
                monitor.worked(1);
        }

        // Also check the sql for the logtable...
        if (monitor != null)
            monitor.subTask(Messages.getString("JobMeta.Monitor.GettingSQLStatementsForJobLogTables")); //$NON-NLS-1$
        if (logConnection != null && logTable != null && logTable.length() > 0) {
            Database db = new Database(logConnection);
            try {
                db.connect();
                RowMetaInterface fields = Database.getJobLogrecordFields(false, useBatchId, logfieldUsed);
                String sql = db.getDDL(logTable, fields);
                if (sql != null && sql.length() > 0) {
                    SQLStatement stat = new SQLStatement(Messages.getString("JobMeta.SQLFeedback.ThisJob"), //$NON-NLS-1$
                            logConnection, sql);
                    stats.add(stat);
                }
            } catch (KettleDatabaseException dbe) {
                SQLStatement stat = new SQLStatement(Messages.getString("JobMeta.SQLFeedback.ThisJob"), //$NON-NLS-1$
                        logConnection, null);
                stat.setError(
                        Messages.getString("JobMeta.SQLFeedback.ErrorObtainingJobLogTableInfo") + dbe.getMessage()); //$NON-NLS-1$
                stats.add(stat);
            } finally {
                db.disconnect();
            }
        }
        if (monitor != null)
            monitor.worked(1);
        if (monitor != null)
            monitor.done();

        return stats;
    }

    /**
     * @return Returns the logTable.
     */
    public String getLogTable() {
        return logTable;
    }

    /**
     * @param logTable
     *            The logTable to set.
     */
    public void setLogTable(String logTable) {
        this.logTable = logTable;
    }

    /**
     * @return Returns the arguments.
     */
    public String[] getArguments() {
        return arguments;
    }

    /**
     * @param arguments
     *            The arguments to set.
     */
    public void setArguments(String[] arguments) {
        this.arguments = arguments;
    }

    /**
     * Get a list of all the strings used in this job.
     * 
     * @return A list of StringSearchResult with strings used in the job
     */
    public List<StringSearchResult> getStringList(boolean searchSteps, boolean searchDatabases,
            boolean searchNotes) {
        List<StringSearchResult> stringList = new ArrayList<StringSearchResult>();

        if (searchSteps) {
            // Loop over all steps in the transformation and see what the used
            // vars are...
            for (int i = 0; i < nrJobEntries(); i++) {
                JobEntryCopy entryMeta = getJobEntry(i);
                stringList.add(new StringSearchResult(entryMeta.getName(), entryMeta, this,
                        Messages.getString("JobMeta.SearchMetadata.JobEntryName"))); //$NON-NLS-1$
                if (entryMeta.getDescription() != null)
                    stringList.add(new StringSearchResult(entryMeta.getDescription(), entryMeta, this,
                            Messages.getString("JobMeta.SearchMetadata.JobEntryDescription"))); //$NON-NLS-1$
                JobEntryInterface metaInterface = entryMeta.getEntry();
                StringSearcher.findMetaData(metaInterface, 1, stringList, entryMeta, this);
            }
        }

        // Loop over all steps in the transformation and see what the used vars
        // are...
        if (searchDatabases) {
            for (int i = 0; i < nrDatabases(); i++) {
                DatabaseMeta meta = getDatabase(i);
                stringList.add(new StringSearchResult(meta.getName(), meta, this,
                        Messages.getString("JobMeta.SearchMetadata.DatabaseConnectionName"))); //$NON-NLS-1$
                if (meta.getDatabaseName() != null)
                    stringList.add(new StringSearchResult(meta.getDatabaseName(), meta, this,
                            Messages.getString("JobMeta.SearchMetadata.DatabaseName"))); //$NON-NLS-1$
                if (meta.getUsername() != null)
                    stringList.add(new StringSearchResult(meta.getUsername(), meta, this,
                            Messages.getString("JobMeta.SearchMetadata.DatabaseUsername"))); //$NON-NLS-1$
                if (meta.getDatabaseTypeDesc() != null)
                    stringList.add(new StringSearchResult(meta.getDatabaseTypeDesc(), meta, this,
                            Messages.getString("JobMeta.SearchMetadata.DatabaseTypeDescription"))); //$NON-NLS-1$
                if (meta.getDatabasePortNumberString() != null)
                    stringList.add(new StringSearchResult(meta.getDatabasePortNumberString(), meta, this,
                            Messages.getString("JobMeta.SearchMetadata.DatabasePort"))); //$NON-NLS-1$
            }
        }

        // Loop over all steps in the transformation and see what the used vars
        // are...
        if (searchNotes) {
            for (int i = 0; i < nrNotes(); i++) {
                NotePadMeta meta = getNote(i);
                if (meta.getNote() != null)
                    stringList.add(new StringSearchResult(meta.getNote(), meta, this,
                            Messages.getString("JobMeta.SearchMetadata.NotepadText"))); //$NON-NLS-1$
            }
        }

        return stringList;
    }

    public List<String> getUsedVariables() {
        // Get the list of Strings.
        List<StringSearchResult> stringList = getStringList(true, true, false);

        List<String> varList = new ArrayList<String>();

        // Look around in the strings, see what we find...
        for (StringSearchResult result : stringList) {
            StringUtil.getUsedVariables(result.getString(), varList, false);
        }

        return varList;
    }

    /**
     * Get an array of all the selected job entries
     * 
     * @return A list containing all the selected & drawn job entries.
     */
    public List<GUIPositionInterface> getSelectedDrawnJobEntryList() {
        List<GUIPositionInterface> list = new ArrayList<GUIPositionInterface>();

        for (int i = 0; i < nrJobEntries(); i++) {
            JobEntryCopy jobEntryCopy = getJobEntry(i);
            if (jobEntryCopy.isDrawn() && jobEntryCopy.isSelected()) {
                list.add(jobEntryCopy);
            }

        }
        return list;
    }

    public boolean haveConnectionsChanged() {
        if (changedDatabases)
            return true;

        for (int i = 0; i < nrDatabases(); i++) {
            DatabaseMeta ci = getDatabase(i);
            if (ci.hasChanged())
                return true;
        }
        return false;
    }

    public boolean haveJobEntriesChanged() {
        if (changedEntries)
            return true;

        for (int i = 0; i < nrJobEntries(); i++) {
            JobEntryCopy entry = getJobEntry(i);
            if (entry.hasChanged())
                return true;
        }
        return false;
    }

    public boolean haveJobHopsChanged() {
        if (changedHops)
            return true;

        for (JobHopMeta hi : jobhops) // Look at all the hops
        {
            if (hi.hasChanged())
                return true;
        }
        return false;
    }

    public boolean haveNotesChanged() {
        if (changedNotes)
            return true;

        for (int i = 0; i < nrNotes(); i++) {
            NotePadMeta note = getNote(i);
            if (note.hasChanged())
                return true;
        }
        return false;
    }

    /**
     * @return the sharedObjectsFile
     */
    public String getSharedObjectsFile() {
        return sharedObjectsFile;
    }

    /**
     * @param sharedObjectsFile
     *            the sharedObjectsFile to set
     */
    public void setSharedObjectsFile(String sharedObjectsFile) {
        this.sharedObjectsFile = sharedObjectsFile;
    }

    /**
     * @param modifiedUser
     *            The modifiedUser to set.
     */
    public void setModifiedUser(String modifiedUser) {
        this.modifiedUser = modifiedUser;
    }

    /**
     * @return Returns the modifiedUser.
     */
    public String getModifiedUser() {
        return modifiedUser;
    }

    /**
     * @param modifiedDate
     *            The modifiedDate to set.
     */
    public void setModifiedDate(Date modifiedDate) {
        this.modifiedDate = modifiedDate;
    }

    /**
     * @return Returns the modifiedDate.
     */
    public Date getModifiedDate() {
        return modifiedDate;
    }

    /**
     * @return The description of the job
     */
    public String getDescription() {
        return description;
    }

    /**
     * @return The extended description of the job
     */
    public String getExtendedDescription() {
        return extendedDescription;
    }

    /**
     * @return The version of the job
     */
    public String getJobversion() {
        return jobVersion;
    }

    /**
     * Get the status of the job
     */
    public int getJobstatus() {
        return jobStatus;
    }

    /**
     * Set the description of the job.
     * 
     * @param description
     *            The new description of the job
     */
    public void setDescription(String description) {
        this.description = description;
    }

    /**
     * Set the description of the job.
     * 
     * @param extendedDescription
     *            The new extended description of the job
     */
    public void setExtendedDescription(String extendedDescription) {
        this.extendedDescription = extendedDescription;
    }

    /**
     * Set the version of the job.
     * 
     * @param jobVersion
     *            The new version description of the job
     */
    public void setJobversion(String jobVersion) {
        this.jobVersion = jobVersion;
    }

    /**
     * Set the status of the job.
     * 
     * @param jobStatus
     *            The new status description of the job
     */
    public void setJobstatus(int jobStatus) {
        this.jobStatus = jobStatus;
    }

    /**
     * @return Returns the createdDate.
     */
    public Date getCreatedDate() {
        return created_date;
    }

    /**
     * @param createdDate
     *            The createdDate to set.
     */
    public void setCreatedDate(Date createdDate) {
        created_date = createdDate;
    }

    /**
     * @param createdUser
     *            The createdUser to set.
     */
    public void setCreatedUser(String createdUser) {
        created_user = createdUser;
    }

    /**
     * @return Returns the createdUser.
     */
    public String getCreatedUser() {
        return created_user;
    }

    /**
     * Find a jobentry with a certain ID in a list of job entries.
     * 
     * @param jobentries
     *            The List of jobentries
     * @param id_jobentry
     *            The id of the jobentry
     * @return The JobEntry object if one was found, null otherwise.
     */
    public static final JobEntryInterface findJobEntry(List<JobEntryInterface> jobentries, long id_jobentry) {
        if (jobentries == null)
            return null;

        for (JobEntryInterface je : jobentries) {
            if (je.getID() == id_jobentry) {
                return je;
            }
        }
        return null;
    }

    /**
     * Find a jobentrycopy with a certain ID in a list of job entry copies.
     * 
     * @param jobcopies
     *            The List of jobentry copies
     * @param id_jobentry_copy
     *            The id of the jobentry copy
     * @return The JobEntryCopy object if one was found, null otherwise.
     */
    public static final JobEntryCopy findJobEntryCopy(List<JobEntryCopy> jobcopies, long id_jobentry_copy) {
        if (jobcopies == null)
            return null;

        for (JobEntryCopy jec : jobcopies) {
            if (jec.getID() == id_jobentry_copy) {
                return jec;
            }
        }
        return null;
    }

    /**
     * Calls setInternalKettleVariables on the default object.
     */
    public void setInternalKettleVariables() {
        setInternalKettleVariables(variables);
    }

    /**
     * This method sets various internal kettle variables that can be used by
     * the transformation.
     */
    public void setInternalKettleVariables(VariableSpace var) {
        if (filename != null) // we have a filename that's defined.
        {
            try {
                FileObject fileObject = KettleVFS.getFileObject(filename);
                FileName fileName = fileObject.getName();

                // The filename of the transformation
                var.setVariable(Const.INTERNAL_VARIABLE_JOB_FILENAME_NAME, fileName.getBaseName());

                // The directory of the transformation
                FileName fileDir = fileName.getParent();
                var.setVariable(Const.INTERNAL_VARIABLE_JOB_FILENAME_DIRECTORY, fileDir.getURI());
            } catch (IOException e) {
                var.setVariable(Const.INTERNAL_VARIABLE_JOB_FILENAME_DIRECTORY, "");
                var.setVariable(Const.INTERNAL_VARIABLE_JOB_FILENAME_NAME, "");
            }
        } else {
            var.setVariable(Const.INTERNAL_VARIABLE_JOB_FILENAME_DIRECTORY, ""); //$NON-NLS-1$
            var.setVariable(Const.INTERNAL_VARIABLE_JOB_FILENAME_NAME, ""); //$NON-NLS-1$
        }

        // The name of the job
        var.setVariable(Const.INTERNAL_VARIABLE_JOB_NAME, Const.NVL(name, "")); //$NON-NLS-1$

        // The name of the directory in the repository
        var.setVariable(Const.INTERNAL_VARIABLE_JOB_REPOSITORY_DIRECTORY,
                directory != null ? directory.getPath() : ""); //$NON-NLS-1$

        // Undefine the transformation specific variables:
        // transformations can't run jobs, so if you use these they are 99.99%
        // wrong.
        var.setVariable(Const.INTERNAL_VARIABLE_TRANSFORMATION_FILENAME_DIRECTORY, null);
        var.setVariable(Const.INTERNAL_VARIABLE_TRANSFORMATION_FILENAME_NAME, null);
        var.setVariable(Const.INTERNAL_VARIABLE_TRANSFORMATION_FILENAME_DIRECTORY, null);
        var.setVariable(Const.INTERNAL_VARIABLE_TRANSFORMATION_FILENAME_NAME, null);
        var.setVariable(Const.INTERNAL_VARIABLE_TRANSFORMATION_NAME, null);
        var.setVariable(Const.INTERNAL_VARIABLE_TRANSFORMATION_REPOSITORY_DIRECTORY, null);
    }

    public void copyVariablesFrom(VariableSpace space) {
        variables.copyVariablesFrom(space);
    }

    public String environmentSubstitute(String aString) {
        return variables.environmentSubstitute(aString);
    }

    public String[] environmentSubstitute(String aString[]) {
        return variables.environmentSubstitute(aString);
    }

    public VariableSpace getParentVariableSpace() {
        return variables.getParentVariableSpace();
    }

    public void setParentVariableSpace(VariableSpace parent) {
        variables.setParentVariableSpace(parent);
    }

    public String getVariable(String variableName, String defaultValue) {
        return variables.getVariable(variableName, defaultValue);
    }

    public String getVariable(String variableName) {
        return variables.getVariable(variableName);
    }

    public boolean getBooleanValueOfVariable(String variableName, boolean defaultValue) {
        if (!Const.isEmpty(variableName)) {
            String value = environmentSubstitute(variableName);
            if (!Const.isEmpty(value)) {
                return ValueMeta.convertStringToBoolean(value);
            }
        }
        return defaultValue;
    }

    public void initializeVariablesFrom(VariableSpace parent) {
        variables.initializeVariablesFrom(parent);
    }

    public String[] listVariables() {
        return variables.listVariables();
    }

    public void setVariable(String variableName, String variableValue) {
        variables.setVariable(variableName, variableValue);
    }

    public void shareVariablesWith(VariableSpace space) {
        variables = space;
    }

    public void injectVariables(Map<String, String> prop) {
        variables.injectVariables(prop);
    }

    /**
     * Check all job entries within the job. Each Job Entry has the opportunity
     * to check their own settings.
     * 
     * @param remarks
     *            List of CheckResult remarks inserted into by each JobEntry
     * @param only_selected
     *            true if you only want to check the selected jobs
     * @param monitor
     *            Progress monitor (not presently in use)
     */
    public void checkJobEntries(List<CheckResultInterface> remarks, boolean only_selected,
            ProgressMonitorListener monitor) {
        remarks.clear(); // Empty remarks
        if (monitor != null)
            monitor.beginTask(Messages.getString("JobMeta.Monitor.VerifyingThisJobEntryTask.Title"), //$NON-NLS-1$
                    jobcopies.size() + 2);
        boolean stop_checking = false;
        for (int i = 0; i < jobcopies.size() && !stop_checking; i++) {
            JobEntryCopy copy = jobcopies.get(i); // get the job entry copy
            if ((!only_selected) || (only_selected && copy.isSelected())) {
                JobEntryInterface entry = copy.getEntry();
                if (entry != null) {
                    if (monitor != null)
                        monitor.subTask(
                                Messages.getString("JobMeta.Monitor.VerifyingJobEntry.Title", entry.getName())); //$NON-NLS-1$ //$NON-NLS-2$
                    entry.check(remarks, this);
                    if (monitor != null) {
                        monitor.worked(1); // progress bar...
                        if (monitor.isCanceled()) {
                            stop_checking = true;
                        }
                    }
                }
            }
            if (monitor != null) {
                monitor.worked(1);
            }
        }
        if (monitor != null) {
            monitor.done();
        }
    }

    public List<ResourceReference> getResourceDependencies() {
        List<ResourceReference> resourceReferences = new ArrayList<ResourceReference>();
        JobEntryCopy copy = null;
        JobEntryInterface entry = null;
        for (int i = 0; i < jobcopies.size(); i++) {
            copy = jobcopies.get(i); // get the job entry copy
            entry = copy.getEntry();
            resourceReferences.addAll(entry.getResourceDependencies(this));
        }

        return resourceReferences;
    }

    public String exportResources(VariableSpace space, Map<String, ResourceDefinition> definitions,
            ResourceNamingInterface namingInterface) throws KettleException {
        String resourceName = null;
        try {
            FileObject fileObject = KettleVFS.getFileObject(getFilename());
            resourceName = namingInterface.nameResource(fileObject.getName().getBaseName(),
                    fileObject.getParent().getName().getPath(), "kjb"); //$NON-NLS-1$
            ResourceDefinition definition = definitions.get(resourceName);
            if (definition == null) {
                // If we do this once, it will be plenty :-)
                //
                JobMeta jobMeta = (JobMeta) this.realClone(false);

                // Add used resources, modify transMeta accordingly
                // Go through the list of steps, etc.
                // These critters change the steps in the cloned TransMeta
                // At the end we make a new XML version of it in "exported"
                // format...

                // loop over steps, databases will be exported to XML anyway.
                //
                for (JobEntryCopy jobEntry : jobMeta.jobcopies) {
                    jobEntry.getEntry().exportResources(jobMeta, definitions, namingInterface);
                }

                // At the end, add ourselves to the map...
                //
                String jobMetaContent = jobMeta.getXML();

                definition = new ResourceDefinition(resourceName, jobMetaContent);
                definitions.put(fileObject.getName().getPath(), definition);
            }
        } catch (FileSystemException e) {
            throw new KettleException(
                    Messages.getString("JobMeta.Exception.AnErrorOccuredReadingJob", getFilename()), e);
        } catch (IOException e) {
            throw new KettleException(
                    Messages.getString("JobMeta.Exception.AnErrorOccuredReadingJob", getFilename()), e);
        }

        return resourceName;
    }

    /**
     * @return the slaveServer list
     */
    public List<SlaveServer> getSlaveServers() {
        return slaveServers;
    }

    /**
     * @param slaveServers
     *            the slaveServers to set
     */
    public void setSlaveServers(List<SlaveServer> slaveServers) {
        this.slaveServers = slaveServers;
    }

    /**
     * Find a slave server using the name
     * 
     * @param serverString
     *            the name of the slave server
     * @return the slave server or null if we couldn't spot an approriate entry.
     */
    public SlaveServer findSlaveServer(String serverString) {
        return SlaveServer.findSlaveServer(slaveServers, serverString);
    }

    /**
     * @return An array list slave server names
     */
    public String[] getSlaveServerNames() {
        return SlaveServer.getSlaveServerNames(slaveServers);
    }

    /**
     * See if the name of the supplied job entry copy doesn't collide with any
     * other job entry copy in the job.
     * 
     * @param je
     *            The job entry copy to verify the name for.
     */
    public void renameJobEntryIfNameCollides(JobEntryCopy je) {
        // First see if the name changed.
        // If so, we need to verify that the name is not already used in the
        // job.
        //
        String newname = je.getName();

        // See if this name exists in the other job entries
        //
        boolean found;
        int nr = 1;
        do {
            found = false;
            for (JobEntryCopy copy : jobcopies) {
                if (copy != je && copy.getName().equalsIgnoreCase(newname) && copy.getNr() == 0)
                    found = true;
            }
            if (found) {
                nr++;
                newname = je.getName() + " (" + nr + ")";
            }
        } while (found);

        // Rename if required.
        //
        je.setName(newname);
    }

    /**
     * @return the sharedObjects
     */
    public SharedObjects getSharedObjects() {
        return sharedObjects;
    }

    /**
     * @param sharedObjects
     *            the sharedObjects to set
     */
    public void setSharedObjects(SharedObjects sharedObjects) {
        this.sharedObjects = sharedObjects;
    }

    public void addNameChangedListener(NameChangedListener listener) {
        if (nameChangedListeners == null) {
            nameChangedListeners = new ArrayList<NameChangedListener>();
        }
        nameChangedListeners.add(listener);
    }

    public void removeNameChangedListener(NameChangedListener listener) {
        nameChangedListeners.remove(listener);
    }

    public void addFilenameChangedListener(FilenameChangedListener listener) {
        if (filenameChangedListeners == null) {
            filenameChangedListeners = new ArrayList<FilenameChangedListener>();
        }
        filenameChangedListeners.add(listener);
    }

    public void removeFilenameChangedListener(FilenameChangedListener listener) {
        filenameChangedListeners.remove(listener);
    }

    private boolean nameChanged(String oldFilename, String newFilename) {
        if (oldFilename == null && newFilename == null)
            return false;
        if (oldFilename == null && newFilename != null)
            return true;
        return oldFilename.equals(newFilename);
    }

    private void fireFilenameChangedListeners(String oldFilename, String newFilename) {
        if (nameChanged(oldFilename, newFilename)) {
            if (filenameChangedListeners != null) {
                for (FilenameChangedListener listener : filenameChangedListeners) {
                    listener.filenameChanged(this, oldFilename, newFilename);
                }
            }
        }
    }

    private void fireNameChangedListeners(String oldName, String newName) {
        if (nameChanged(oldName, newName)) {
            if (nameChangedListeners != null) {
                for (NameChangedListener listener : nameChangedListeners) {
                    listener.nameChanged(this, oldName, newName);
                }
            }
        }
    }

    public int getGuiLocationX() {
        return guiLocationX;
    }

    public void setGuiLocationX(int guiLocationX) {
        this.guiLocationX = guiLocationX;
    }

    public int getGuiLocationY() {
        return guiLocationY;
    }

    public void setGuiLocationY(int guiLocationY) {
        this.guiLocationY = guiLocationY;
    }

    public double getGuiScale() {
        return guiScale;
    }

    public void setGuiScale(double guiScale) {
        this.guiScale = guiScale;
    }

    public Repository getRep() {
        return rep;
    }

    public void setRep(Repository rep) {
        this.rep = rep;
    }

    public void activateParameters() {
        String[] keys = listParameters();

        for (String key : keys) {
            String value = getParameterValue(key);
            setVariable(key, value);
        }
    }

    public void addParameterDefinition(String key, String description) {
        namedParams.addParameterDefinition(key, description);
    }

    public String getParameterDescription(String key) {
        return namedParams.getParameterDescription(key);
    }

    public String getParameterValue(String key) {
        return namedParams.getParameterValue(key);
    }

    public String[] listParameters() {
        return namedParams.listParameters();
    }

    public void setParameterValue(String key, String value) {
        namedParams.setParameterValue(key, value);
    }

    public void clearValues() {
        namedParams.clearValues();
    }

    public void copyParametersFrom(NamedParams params) {
        namedParams.copyParametersFrom(params);
    }

    public NamedParams getNamedParams() {
        return namedParams;
    }

    public void setNamedParams(NamedParams namedParams) {
        this.namedParams = namedParams;
    }
}