public interface ArchitectSession extends UserPrompterFactory, SQLDatabaseMapping, WorkspaceContainer, RunnableDispatcher {

    public static final String PREFS_PL_INI_PATH = "PL.INI.PATH";
    
    public ProfileManager getProfileManager();
    
    /**
     * Returns the context that created this session.
     */
    public ArchitectSessionContext getContext();
    
    /**
     * Returns the database in use for this session. In a 
     * gui session, this would be the playpen database. 
     */
    public SQLDatabase getTargetDatabase();
    
    /**
     * Sets the value of name
     *
     * @param argName Value to assign to this.name
     */
    public void setName(String argName);
    
    /**
     * Gets the value of name
     *
     * @return the value of name
     */
    public String getName();
    
    /**
     * Returns the project associated with this session.  The project
     * holds the playpen objects, and can save and load itself in an
     * XML format.
     */
    public ProjectLoader getProjectLoader();
    
    /**
     *  This method is only used to create the correct type of project
     *  for an ArchitectSwingSessionImpl and should not be called anywhere
     *  else.
     */
    public void setProjectLoader(ProjectLoader project);
    
    
    /**
     *  Replaces the entire list of source databases for this session.
     *  This method is used reflectively by the code that does loading and saving,
     *  so DON'T DELETE THIS METHOD even if it looks like it's unused.
     * 
     * @param databases
     * @throws SQLObjectException
     */
    public void setSourceDatabaseList(List<SQLDatabase> databases) throws SQLObjectException;
    
    /**
     * The DDL Generator currently in use for this session.
     */
    public DDLGenerator getDDLGenerator();
    
    /**
     * Sets the new DDL Generator currently in use for this session.
     */
    public void setDDLGenerator(DDLGenerator generator);    

    /**
     * Returns the workspace object which is the root object of the ArchitectSession, the ArchitectProject.  
     */
    public ArchitectProject getWorkspace();    
    
    /**
     * Returns the root SQL object of the session, which is the tree that contains Columns, Databases, etc.
     */
    public SQLObjectRoot getRootObject();
    
    public DataSourceCollection<JDBCDataSource> getDataSources();
    
    public boolean isEnterpriseSession();

    public void addSessionLifecycleListener(SessionLifecycleListener<ArchitectSession> l);

    public void removeSessionLifecycleListener(SessionLifecycleListener<ArchitectSession> l);        
    
    /**
     * Ends this session, disposing its frame and releasing any system
     * resources that were obtained explicitly by this session. Also
     * fires a sessionClosing lifecycle event, so any resources used up
     * by subsystems dependent on this session can be freed by the appropriate
     * parties.
     * 
     * @return True if the session was successfully closed. False if the
     * session did not close due to an error or user intervention.
     */
    public boolean close();
    
    /**
     * Returns a list of {@link UserDefinedSQLType} defined in this session.
     */
    public List<UserDefinedSQLType> getSQLTypes();

    /**
     * Returns a list of {@link UserDefinedSQLType}s defined in this session. Only
     * enterprise sessions should contain domains.
     * @return List of UserDefinedSQLType
     */
    public List<UserDefinedSQLType> getDomains();
    
    /**
     * Searches the session for a {@link UserDefinedSQLType} with the given
     * UUID.
     * 
     * @param uuid
     *            The UUID of the desired {@link UserDefinedSQLType}.
     * @return Returns a {@link UserDefinedSQLType} with the given UUID if it
     *         exists in this session. If not, then it will return null.
     */
    public UserDefinedSQLType findSQLTypeByUUID(String uuid);
    
    /**
     * Searches the session for a {@link UserDefinedSQLType} with the given int
     * that represents a value from {@link java.sql.Types}.
     * 
     * @param type
     *            The {@link Types} value of the {@link UserDefinedSQLType}
     *            desired.
     * @return Returns a {@link UserDefinedSQLType} with the given type if one
     *         exists in this session. If not, then it will return null.
     */
    public UserDefinedSQLType findSQLTypeByJDBCType(int type);

	/**
	 * Return the LiquibaseSettings to be used for forward engineering a model.
	 * <br>
	 * The settings for the model compared are stored in CompareDMSettings
	 * @return liquibase settings for forward engineering
	 * @see ca.sqlpower.architect.swingui.CompareDMSettings#getLiquibaseSettings()
	 */
	public LiquibaseSettings getLiquibaseSettings();

	/**
	 * Store the LiquibaseSettings for forward engineering a model.
	 * Settings for model compare are stored in CompareDMSettings.
	 * @param settings Liquibase settings
	 */
	public void setLiquibaseSettings(LiquibaseSettings settings);

    /**
     * Returns the status information object to update the user on different
     * kinds of progress. May return null if no status information exists (for
     * headless mode).
     * 
     * @return architectStatusInformation
     */
	public ArchitectStatusInformation getStatusInformation();
	
	/**
     * Returns a runnable that will update the snapshot object in the given
     * snapshot to be the same as the type the snapshot maps to in the system
     * workspace.
     * 
     * @param snapshot
     *            The snapshot to update. This currently only works for
     *            snapshots of UserDefinedSQLTypes but will be extended to
     *            others in the future.
     */
    public Runnable createUpdateSnapshotRunnable(final SPObjectSnapshot<?> snapshot);

} public class CoreUserSettings {
    private static final Logger logger = Logger.getLogger(CoreUserSettings.class);

    /**
     * The prefs node we read and write all the settings in.
     */
    private final Preferences prefs;
    
	/**
	 * For now, this just holds the preferred printer.  
	 */
    private PrintUserSettings printUserSettings;

	/**
	 * GUI-related settings.  This technically shouldn't be here
	 * (model is referencing view stuff) but it didn't seem right to
	 * make the settings file primarily swing-specific with a
	 * reference to the general architect prefs.
	 */
    private UserSettings swingSettings;

	/**
	 * ETL-related settings.  This is not a design problem like
	 * swingSettings is, since ETL is part of the app's core
	 * functionality.
	 */
    private ETLUserSettings etlUserSettings;

	/**
	 * DDL-related settings.  
	 */
    private DDLUserSettings ddlUserSettings;

    private QFAUserSettings qfaUserSettings;
    
    private UpdateCheckSettings updateCheckSettings;

    private Locale defaultLocale = Locale.getDefault();
	
    public CoreUserSettings(Preferences prefs) {
		super();
		printUserSettings = new PrintUserSettings();
		swingSettings = new ArchitectSwingUserSettings();
		etlUserSettings = new ETLUserSettings();
		ddlUserSettings = new DDLUserSettings();
        qfaUserSettings = new QFAUserSettings();
        setUpdateCheckSettings(new UpdateCheckSettings());
        this.prefs = prefs;
        loadFromPrefs();
	}
	
    /**
     * Sets up the various prefs objects based on values stored in the java.util.prefs
     * preferences node.
     */
    private final void loadFromPrefs() {
        logger.debug("loading UserSettings from java.util.prefs.");
        logger.debug("Preferences class = " + prefs.getClass());

        swingSettings.setBoolean(ArchitectSwingUserSettings.PLAYPEN_RENDER_ANTIALIASED,
                prefs.getBoolean(ArchitectSwingUserSettings.PLAYPEN_RENDER_ANTIALIASED, false));
        if (getDefaultLocale() != null) {
            String localeVal = prefs.get(ArchitectSwingUserSettings.DEFAULT_LOCALE, getDefaultLocale().getDisplayLanguage());
            swingSettings.setString(ArchitectSwingUserSettings.DEFAULT_LOCALE,localeVal);
            setDefaultLocale(new Locale(localeVal));
        }  else {
            swingSettings.setString(ArchitectSwingUserSettings.DEFAULT_LOCALE,Locale.getDefault().getDisplayName());
        }
        etlUserSettings.setString(ETLUserSettings.PROP_PL_ENGINE_PATH,
            prefs.get(ETLUserSettings.PROP_PL_ENGINE_PATH, ""));
        etlUserSettings.setString(ETLUserSettings.PROP_ETL_LOG_PATH,
            prefs.get(ETLUserSettings.PROP_ETL_LOG_PATH, defaultHomeFile("etl.log")));

        ddlUserSettings.setString(DDLUserSettings.PROP_DDL_LOG_PATH,prefs.get(DDLUserSettings.PROP_DDL_LOG_PATH, defaultHomeFile("ddl.log")));

        qfaUserSettings.setBoolean(QFAUserSettings.EXCEPTION_REPORTING,prefs.getBoolean(QFAUserSettings.EXCEPTION_REPORTING,true));
        
        getUpdateCheckSettings().setBoolean(UpdateCheckSettings.AUTO_UPDATE_CHECK, prefs.getBoolean(UpdateCheckSettings.AUTO_UPDATE_CHECK, true));

        printUserSettings.setDefaultPrinterName(
                prefs.get(PrintUserSettings.DEFAULT_PRINTER_NAME, ""));
    }
    
    /**
     * Saves all the preferences that this class knows about back into the
     * prefs node.
     */
    public void write() throws SQLObjectException {
        logger.debug("Saving user settings to java.util.prefs");

        prefs.putBoolean(ArchitectSwingUserSettings.PLAYPEN_RENDER_ANTIALIASED,
                swingSettings.getBoolean(ArchitectSwingUserSettings.PLAYPEN_RENDER_ANTIALIASED, false));

        prefs.put(ETLUserSettings.PROP_PL_ENGINE_PATH, etlUserSettings.getString(ETLUserSettings.PROP_PL_ENGINE_PATH,""));
        prefs.put(ETLUserSettings.PROP_ETL_LOG_PATH, etlUserSettings.getString(ETLUserSettings.PROP_ETL_LOG_PATH,""));

        prefs.put(DDLUserSettings.PROP_DDL_LOG_PATH, ddlUserSettings.getString(DDLUserSettings.PROP_DDL_LOG_PATH,""));

        prefs.putBoolean(QFAUserSettings.EXCEPTION_REPORTING,qfaUserSettings.getBoolean(QFAUserSettings.EXCEPTION_REPORTING,true));
        
        prefs.putBoolean(UpdateCheckSettings.AUTO_UPDATE_CHECK, getUpdateCheckSettings().getBoolean(UpdateCheckSettings.AUTO_UPDATE_CHECK, true));

        prefs.put(PrintUserSettings.DEFAULT_PRINTER_NAME, printUserSettings.getDefaultPrinterName());
        
        prefs.put(ArchitectSwingUserSettings.DEFAULT_LOCALE, swingSettings.getString(ArchitectSwingUserSettings.DEFAULT_LOCALE, getDefaultLocale().toString()));

        try {
            prefs.flush();
        } catch (BackingStoreException e) {
            throw new SQLObjectException("Unable to flush Java preferences", e);
        }
    }
    
    /**
     * Creates a full pathname for a file of the given name inside the user's
     * home directory.
     *  
     * @param name The file name.
     */
    private String defaultHomeFile(String name) {
        return System.getProperty("user.home") + System.getProperty("file.separator") + name;
    }
    
	public void setPrintUserSettings (PrintUserSettings printUserSettings) {
		this.printUserSettings = printUserSettings;
	}

	public PrintUserSettings getPrintUserSettings () {
		return this.printUserSettings;
	}

	public UserSettings getSwingSettings() {
		return swingSettings;
	}

	public void setSwingSettings(UserSettings sprefs) {
		swingSettings = sprefs;
	}

	public ETLUserSettings getETLUserSettings() {
		return etlUserSettings;
	}

	public void setETLUserSettings(ETLUserSettings v) {
		etlUserSettings = v;
	}

	public QFAUserSettings getQfaUserSettings() {
        return qfaUserSettings;
    }

    public void setQfaUserSettings(QFAUserSettings qfaUserSettings) {
        this.qfaUserSettings = qfaUserSettings;
    }

    public DDLUserSettings getDDLUserSettings() {
		return ddlUserSettings;
	}

	public void setDDLUserSettings(DDLUserSettings v) {
		ddlUserSettings = v;
	}

    public void setUpdateCheckSettings(UpdateCheckSettings updateCheckSettings) {
        this.updateCheckSettings = updateCheckSettings;
    }

    public UpdateCheckSettings getUpdateCheckSettings() {
        return updateCheckSettings;
    }

    /**
     * 
     * @return current default Locale
     */
    public Locale getDefaultLocale() {
        return defaultLocale;
    }


    /**
     * Method used to set default Language
     * @param defaultLocale the defaultLocale to set
     */
    public void setDefaultLocale(Locale defaultLocale) {
        this.defaultLocale = defaultLocale;
        Locale.setDefault(defaultLocale);
    }

  
  
} public class ArchitectClientSideSession extends ArchitectSessionImpl implements RevisionController {	
	
	private static Logger logger = Logger.getLogger(ArchitectClientSideSession.class);
	
	private static CookieStore cookieStore = new BasicCookieStore();
	
	public static final String MONDRIAN_SCHEMA_REL_PATH = "/mondrian";
	
	/**
	 * The prefs node that will store information about the current settings of
	 * the DDL generator and compare DM panels. Currently this is stored in prefs
	 * because we want to store it per user for each project they are using. In the
	 * future we may want to store this in the server, once per user per project.
	 */
	private final Preferences prefs = Preferences.userNodeForPackage(ArchitectClientSideSession.class);
	
	/**
	 * Describes the location of the project that this session represents.
	 */
	private final ProjectLocation projectLocation;

    /**
     * An {@link HttpClient} used to send updates to the server for changes to
     * the project and to receive updates from other users from the server.
     */
	private final HttpClient outboundHttpClient;
	
	/**
	 * The persister that will update the project in this session with changes from
	 * the server.
	 */
	private final ArchitectSessionPersister sessionPersister;
	
	/**
	 * Used to convert JSON sent from the server into persist calls to forward the
	 * server changes to the {@link #sessionPersister}.
	 */
	private final SPJSONPersister jsonPersister;
	private final ArchitectNetworkConflictResolver updater;
	private final SPJSONMessageDecoder jsonMessageDecoder;
	private final DataSourceCollectionUpdater dataSourceCollectionUpdater;
	
	private DataSourceCollection <JDBCDataSource> dataSourceCollection;

	/**
	 * Used to store sessions which hold nothing but security info.
	 */
	public static Map<String, ArchitectClientSideSession> securitySessions;
	
    private AbstractPoolingSPListener deletionListener;

    /**
     * The executor to use as the foreground thread manager. If this is not null
     * and there is no EDT available this executor will be used to ensure the
     * system is single threaded.
     */
    private final ThreadPoolExecutor foregroundThreadExecutor;

    /**
     * This is false except for testing purposes when the single thread executor
     * may want to be used if the test is running headless.
     */
    private final boolean useThreadPool;
    
    /**
     * The threads used by {@link #foregroundThreadExecutor} to keep Architect
     * single threaded if we are using the {@link #foregroundThreadExecutor}.
     * The executor will only allow one of these threads to be used at a time.
     */
    private final Set<Thread> foregroundExecutorThread = Collections.synchronizedSet(new HashSet<Thread>());
    
    static {
        securitySessions = new HashMap<String, ArchitectClientSideSession>();
    }
	
	public ArchitectClientSideSession(ArchitectSessionContext context, 
			String name, ProjectLocation projectLocation) throws SQLObjectException {
	    this(context, name, projectLocation, false);
	}

    /**
     * This constructor is only used for testing. This constructor allows users
     * to specify an executor to use as the foreground thread instead of using
     * the normal EDT. This is handy for ensuring all of the events occur on the
     * correct thread and updates do not conflict with persists. If the executor
     * is null then the foreground thread will just execute the runnables on the
     * current thread.
     */
	public ArchitectClientSideSession(ArchitectSessionContext context,
	        String name, ProjectLocation projectLocation, boolean useThreadPool) throws SQLObjectException {
		super(context, name, new ArchitectSwingProject());
		
		this.projectLocation = projectLocation;
        this.useThreadPool = useThreadPool;
        this.foregroundThreadExecutor = new ThreadPoolExecutor(1, 1, 5, TimeUnit.MINUTES, 
                new LinkedBlockingQueue<Runnable>(), new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread newThread = new Thread(r);
                        foregroundExecutorThread.add(newThread);
                        return newThread;
                    }
                });
        foregroundThreadExecutor.allowCoreThreadTimeOut(false);
		dataSourceCollectionUpdater = new ArchitectDataSourceCollectionUpdater(projectLocation);
		this.isEnterpriseSession = true;
		
		setupSnapshots();
		
		String ddlgClass = prefs.get(this.projectLocation.getUUID() + ".ddlg", null);
		if (ddlgClass != null) {
		    try {
                DDLGenerator ddlg = (DDLGenerator) Class.forName(ddlgClass, true, ArchitectClientSideSession.class.getClassLoader()).newInstance();
                setDDLGenerator(ddlg);
                ddlg.setTargetCatalog(prefs.get(this.projectLocation.getUUID() + ".targetCatalog", null));
                ddlg.setTargetSchema(prefs.get(this.projectLocation.getUUID() + ".targetSchema", null));
            } catch (Exception e) {
                createUserPrompter("Cannot load DDL settings due to missing class " + ddlgClass, 
                        UserPromptType.MESSAGE, UserPromptOptions.OK, UserPromptResponse.OK, null, "OK");
                logger.error("Cannot find DDL Generator for class " + ddlgClass + 
                        ", ddl generator properties are not loaded.");
            }
		}
		
		outboundHttpClient = ClientSideSessionUtils.createHttpClient(projectLocation.getServiceInfo(), cookieStore);
		dataSourceCollection = getDataSources();
		
		sessionPersister = new ArchitectSessionPersister("inbound-" + projectLocation.getUUID(), getWorkspace(), 
		        new ArchitectPersisterSuperConverter(dataSourceCollection, getWorkspace()));
		sessionPersister.setWorkspaceContainer(this);
		
		jsonMessageDecoder = new SPJSONMessageDecoder(sessionPersister);
		
		updater = new ArchitectNetworkConflictResolver(
		        projectLocation, 
		        jsonMessageDecoder, 
		        ClientSideSessionUtils.createHttpClient(projectLocation.getServiceInfo(), cookieStore), 
		        outboundHttpClient, this);
		
		jsonPersister = new SPJSONPersister(updater);
		
		verifyServerLicense(projectLocation);
	}

    protected void verifyServerLicense(ProjectLocation projectLocation) throws AssertionError {
        try {
            ServerInfoProvider.getServerVersion(
                    projectLocation.getServiceInfo().getServerAddress(), 
                    String.valueOf(projectLocation.getServiceInfo().getPort()), 
                    projectLocation.getServiceInfo().getPath(), 
                    projectLocation.getServiceInfo().getUsername(), 
                    projectLocation.getServiceInfo().getPassword(),
                    cookieStore);
        } catch (Exception e) {
            throw new AssertionError("Exception encountered while verifying the server license:" + e.getMessage());
        }
    }

    /**
     * Helper method for the constructor of a client side session.
     * <p>
     * This method will attach listeners and update snapshots as necessary on
     * the project's load.
     */
    private void setupSnapshots() {
        getTargetDatabase().addSPListener(new SPObjectSnapshotHierarchyListener(this));
        
        //This listener will update the obsolete flag on snapshots being added
        //to the workspace to keep old snapshot's obsolete flag correct when
        //they haven't been opened in some time but updates have occurred to the types.
        //While this will also check new snapshots being added to the system doing
        //the check doesn't hurt anything.
        final AbstractPoolingSPListener obsolescenceListener = new AbstractPoolingSPListener(false) {
            @Override
            public void childAddedImpl(SPChildEvent e) {
                if (e.getChild() instanceof UserDefinedSQLTypeSnapshot) {
                    UserDefinedSQLTypeSnapshot snapshot = (UserDefinedSQLTypeSnapshot) e.getChild();
                    UserDefinedSQLType systemType = findSystemTypeFromSnapshot(snapshot);
                    if (systemType == null) {
                        snapshot.setDeleted(true);
                    } else { 
                        if (!UserDefinedSQLType.areEqual(snapshot.getSPObject(), systemType)) {
                            snapshot.setObsolete(true);
                        }
                        snapshot.setDeleted(false);
                    }
                } else if (e.getChild() instanceof DomainCategorySnapshot) {
                    DomainCategorySnapshot snapshot = (DomainCategorySnapshot) e.getChild();
                    boolean deleted = true;
                    for (DomainCategory category : getSystemWorkspace().getDomainCategories()) {
                        if (category.getUUID().equals(snapshot.getOriginalUUID())) {
                            deleted = false;
                            if (!DomainCategory.areEqual(snapshot.getSPObject(), category)) {
                                snapshot.setObsolete(true);
                                deleted = true;
                            }
                        }
                    }
                    snapshot.setDeleted(deleted);
                }
            }
        };
        getWorkspace().getSnapshotCollection().addSPListener(obsolescenceListener);
        getWorkspace().addSPListener(new AbstractSPListener() {
            @Override
            public void transactionStarted(TransactionEvent e) {
                obsolescenceListener.transactionStarted(TransactionEvent.createStartTransactionEvent(getWorkspace().getSnapshotCollection(), "Simulated begin: " + e.getMessage()));
            }
            @Override
            public void transactionEnded(TransactionEvent e) {
                obsolescenceListener.transactionEnded(TransactionEvent.createEndTransactionEvent(getWorkspace().getSnapshotCollection(), "Simulated commit: " + e.getMessage()));
            }
            @Override
            public void transactionRollback(TransactionEvent e) {
                obsolescenceListener.transactionRollback(TransactionEvent.createRollbackTransactionEvent(getWorkspace().getSnapshotCollection(), "Simulated rollback: " + e.getMessage()));
            }
        });
        
        // If this returns null, it means the system session hasn't been
        // initialized yet. This means we are the system session, and attaching
        // the listener is unnecessary.
        if (getSystemSession() != null) {
            deletionListener = new AbstractPoolingSPListener() {
                @Override
                protected void childAddedImpl(SPChildEvent e) {
                    if (e.getChild() instanceof DomainCategory) {
                        e.getChild().addSPListener(deletionListener);
                    } else {
                        for (SPObjectSnapshot<SPObject> snapshot : getWorkspace().getSnapshotCollection().getChildren(SPObjectSnapshot.class)) {
                            if (snapshot.getOriginalUUID().equals(e.getChild().getUUID())) {
                                snapshot.setDeleted(false);
                            }
                        }
                    }
                }

                @Override
                protected void childRemovedImpl(SPChildEvent e) {
                    if (e.getChild() instanceof DomainCategory) {
                        e.getChild().removeSPListener(deletionListener);
                    } else {
                        for (SPObjectSnapshot<SPObject> snapshot : getWorkspace().getSnapshotCollection().getChildren(SPObjectSnapshot.class)) {
                            if (snapshot.getOriginalUUID().equals(e.getChild().getUUID())) {
                                snapshot.setDeleted(true);
                            }
                        }
                    }
                }
            };

            getSystemWorkspace().addSPListener(deletionListener);
            for (DomainCategory cat : getSystemWorkspace().getChildren(DomainCategory.class)) {
                cat.addSPListener(deletionListener);
            }
        }
    }

    /**
     * Map of server addresses to system workspaces. Use
     * {@link SPServerInfo#getServerAddress()} as the key.
     */
	public static Map<String, ArchitectClientSideSession> getSecuritySessions() {
        return securitySessions;
    }
	
	@Override
    public boolean close() {
    	if (getDDLGenerator() != null) {
    	    if (getDDLGenerator().getTargetCatalog() != null) {
    	        prefs.put(projectLocation.getUUID() + ".targetCatalog", getDDLGenerator().getTargetCatalog());
    	    }
    	    if (getDDLGenerator().getTargetSchema() != null) {
    	        prefs.put(projectLocation.getUUID() + ".targetSchema", getDDLGenerator().getTargetSchema());
    	    }
    	    prefs.put(projectLocation.getUUID() + ".ddlg", getDDLGenerator().getClass().getName());
    	}
    	
    	try {
    	    //TODO: Figure out how to de-register the session &c.
		} catch (Exception e) {
			try {
				logger.error(e);
				
				createUserPrompter("Cannot access the server to close the server session", 
						UserPromptType.MESSAGE,
						UserPromptOptions.OK, 
						UserPromptResponse.OK, 
						UserPromptResponse.OK, "OK");
				
			} catch (Throwable t) {
				//do nothing here because we failed on logging the error.
			}
		}
		
		updater.interrupt();
        outboundHttpClient.getConnectionManager().shutdown();
        
        if (dataSourceCollection != null) {
            dataSourceCollectionUpdater.detach(dataSourceCollection);
        }
        
        getSystemWorkspace().removeSPListener(deletionListener);
        for (DomainCategory cat : getSystemWorkspace().getChildren(DomainCategory.class)) {
            cat.removeSPListener(deletionListener);
        }
        
        return super.close();
    }

	@Override
	public DataSourceCollection<JDBCDataSource> getDataSources() {
	    if (dataSourceCollection == null) {
            dataSourceCollection = getDataSourcesFromServer();
            dataSourceCollectionUpdater.attach(dataSourceCollection);
        }
	    return dataSourceCollection;
	}
	
	private DataSourceCollection<JDBCDataSource> getDataSourcesFromServer() {
		ResponseHandler<DataSourceCollection<JDBCDataSource>> plIniHandler = 
            new ResponseHandler<DataSourceCollection<JDBCDataSource>>() {
            public DataSourceCollection<JDBCDataSource> handleResponse(HttpResponse response)
                    throws ClientProtocolException, IOException {
                
                if (response.getStatusLine().getStatusCode() == 401) {
                    throw new AccessDeniedException("Access Denied");
                }

                if (response.getStatusLine().getStatusCode() != 200) {
                    throw new IOException(
                            "Server error while reading data sources: " + response.getStatusLine());
                }
                
                
                PlDotIni plIni;
                try {
					plIni = new PlDotIni(ClientSideSessionUtils.getServerURI(projectLocation.getServiceInfo(), "/" + ClientSideSessionUtils.REST_TAG +"/jdbc/"),
					        ClientSideSessionUtils.getServerURI(projectLocation.getServiceInfo(), MONDRIAN_SCHEMA_REL_PATH)) {
					    
					    @Override
					    public List<UserDefinedSQLType> getSQLTypes() {
					        List<UserDefinedSQLType> types = new ArrayList<UserDefinedSQLType>();
					        types.addAll(ArchitectClientSideSession.this.getSQLTypes());
					        types.addAll(ArchitectClientSideSession.this.getDomains());
					        return types;
					    }
					    
					    @Override
					    public UserDefinedSQLType getNewSQLType(String name, int jdbcCode) {
					        UserDefinedSQLType newType = new UserDefinedSQLType();
					        newType.setName(name);
					        newType.setType(jdbcCode);
					        ArchitectSwingProject systemWorkspace = ArchitectClientSideSession.this.getSystemWorkspace();
                            systemWorkspace.addChild(newType, systemWorkspace.getChildren(UserDefinedSQLType.class).size());
                            return newType;
					    }
					    
					    public SPDataSource getDataSource(String name) {
					        SPDataSource ds = super.getDataSource(name);
					        if (ds == null) {
					            mergeNewDataSources();
					            return super.getDataSource(name);
					        } else {
					            return ds;
					        }
					    }
					    
					    public <C extends SPDataSource> C getDataSource(String name, java.lang.Class<C> classType) {
					        C ds = super.getDataSource(name, classType);
                            if (ds == null) {
                                mergeNewDataSources();
                                return super.getDataSource(name, classType);
                            } else {
                                return ds;
                            }
					    }
					    
					    private void mergeNewDataSources() {
					        DataSourceCollection<JDBCDataSource> dsc = getDataSourcesFromServer();
                            for (SPDataSource merge : dsc.getConnections()) {
                                mergeDataSource(merge);
                            }
					    }
					};
                    plIni.read(response.getEntity().getContent());
                    logger.debug("Data source collection has URI " + plIni.getServerBaseURI());
                } catch (URISyntaxException e) {
                    throw new RuntimeException(e);
                }
                
                return new SpecificDataSourceCollection<JDBCDataSource>(plIni, JDBCDataSource.class);
            }
        };
        
        DataSourceCollection<JDBCDataSource> dsc;
        try {
            dsc = ClientSideSessionUtils.executeServerRequest(outboundHttpClient, projectLocation.getServiceInfo(), 
                    "/" + ClientSideSessionUtils.REST_TAG + "/data-sources/", plIniHandler);
        } catch (AccessDeniedException e) {
            throw e;
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
        
        return dsc;
	}
	
	public void startUpdaterThread() {
		
		final SPPersisterListener listener = new SPPersisterListener(jsonPersister, sessionPersister,
						new ArchitectPersisterSuperConverter(dataSourceCollection, getWorkspace()));
		SQLPowerUtils.listenToHierarchy(getWorkspace(), listener);
		
		updater.setListener(listener);
		updater.setConverter(new SessionPersisterSuperConverter(dataSourceCollection, getWorkspace()));
		updater.start();
		
		addSessionLifecycleListener(new SessionLifecycleListener<ArchitectSession>() {
			public void sessionClosing(SessionLifecycleEvent<ArchitectSession> e) {
				SQLPowerUtils.unlistenToHierarchy(getWorkspace(), listener);
			}

            public void sessionOpening(SessionLifecycleEvent<ArchitectSession> e) {
            }
		});
	}
	
	public User getUser() {
	    String username = getProjectLocation().getServiceInfo().getUsername();
	    User currentUser = null;
        for (User user : getSystemWorkspace().getChildren(User.class)) {
            if (user.getUsername().equals(username)) {
                currentUser = user;
            }
        }
	    return currentUser;
	}
	
	public void persistProjectToServer() throws SPPersistenceException {
		final SPPersisterListener tempListener = new SPPersisterListener(jsonPersister,
						new ArchitectPersisterSuperConverter(dataSourceCollection, getWorkspace()));
		tempListener.persistObject(getWorkspace(), 0);
	}
	
	public ArchitectSwingProject getSystemWorkspace() {
		return getSecuritySessions().get(getProjectLocation().getServiceInfo().getServerAddress()).getWorkspace();
	}
	
	public ArchitectClientSideSession getSystemSession() {
	    return getSecuritySessions().get(getProjectLocation().getServiceInfo().getServerAddress());
	}

	@Override
	public void runInForeground(Runnable runner) {
		// If we're in a SwingContext, run on the Swing Event Dispatch thread.
		// XXX: This is a bit of a quickfix and I think a better way to possibly fix
		// this could be to have WabitServerSession implement WabitSession, and
		// use a delegate session to delegate most of the server calls (instead
		// of extending WabitSessionImpl). Then if it's in a swing context, it would
		// have a WabitSwingSession instead.
		if (getContext() instanceof ArchitectSwingSessionContext) {
			SwingUtilities.invokeLater(runner);
		} else if (useThreadPool) {
		    foregroundThreadExecutor.execute(runner);
		} else {
			super.runInForeground(runner);
		}
	}
	
	@Override
	public boolean isForegroundThread() {
	    if (useThreadPool) {
	        return foregroundExecutorThread.contains(Thread.currentThread());
	    } else {
	        return super.isForegroundThread();
	    }
	}
	
	/**
	 * Exposes the shared cookie store so we don't spawn useless sessions
	 * through the client.
	 */
	public static CookieStore getCookieStore() {
		return cookieStore;
	}
	
	public ProjectLocation getProjectLocation() {
		return projectLocation;
	}
	
	public List<TransactionInformation> getTransactionList(long fromVersion, long toVersion)
    throws IOException, URISyntaxException, JSONException, ParseException {
        
        SPServerInfo serviceInfo = projectLocation.getServiceInfo();
        HttpClient httpClient = ClientSideSessionUtils.createHttpClient(serviceInfo, cookieStore);
        
        try {
            
            logger.info("Getting transactions between " + fromVersion + " and " + toVersion);
            JSONMessage message = ClientSideSessionUtils.executeServerRequest(httpClient, projectLocation.getServiceInfo(),
                    "/" + ClientSideSessionUtils.REST_TAG + "/project/" + projectLocation.getUUID() + "/revision_list",
                    "versions=" + fromVersion + ":" + toVersion,
                    new JSONResponseHandler());
            
            return ClientSideSessionUtils.decodeJSONRevisionList(message.getBody());
            
        } finally {
            httpClient.getConnectionManager().shutdown();
        }               
        
    }

	public static ProjectLocation uploadProject(SPServerInfo serviceInfo, String name, File project, UserPrompterFactory session) 
    throws URISyntaxException, ClientProtocolException, IOException, JSONException {
	    return ClientSideSessionUtils.uploadProject(serviceInfo, name, project, session, cookieStore);
	}
	
	public int revertServerWorkspace(int revisionNo) throws IOException, URISyntaxException, JSONException {
	    return revertServerWorkspace(projectLocation, revisionNo);
	}
    
	/**
     * This method reverts the server workspace specified by the given project location
     * to the specified revision number.
     * 
	 * @param projectLocation
	 * @param revisionNo
	 * @return The new global revision number, right after the reversion, or -1 if the server did not revert.
	 * @throws IOException
	 * @throws URISyntaxException
	 * @throws JSONException
	 */
    public static int revertServerWorkspace(ProjectLocation projectLocation, int revisionNo)
    throws IOException, URISyntaxException, JSONException {
        return ClientSideSessionUtils.revertServerWorkspace(projectLocation, revisionNo, cookieStore);
    }

    /**
     * This method can update any users password on the server given the correct
     * old password and done by a user with the privileges to change the user's
     * password.
     * 
     * @param user
     *            The user whose password to update.
     * @param oldPassword
     *            The old password of the user to validate that the password can
     *            be updated correctly.
     * @param newPassword
     *            The new password to update to.
     * @param upf
     *            A user prompter to display message and error information to
     *            the user as necessary.
     */
	public void updateUserPassword(User user, 
	        String oldPassword, String newPassword, UserPrompterFactory upf) {
	    SPServerInfo serviceInfo = getProjectLocation().getServiceInfo();
        
        HttpClient client = ClientSideSessionUtils.createHttpClient(serviceInfo, cookieStore);
        
        MessageDigest digester;
        try {
            digester = MessageDigest.getInstance("SHA-256");
        } catch (NoSuchAlgorithmException ex) {
            throw new RuntimeException(ex);
        }
        
        try {
            JSONObject begin = new JSONObject();
            begin.put("uuid", JSONObject.NULL);
            begin.put("method", "begin");
            
            JSONObject persist = new JSONObject();
            persist.put("uuid", user.getUUID());
            persist.put("propertyName", "password");
            persist.put("type", Datatype.STRING.toString());
            if (oldPassword == null) {
                persist.put("method", "persistProperty");
            } else {
                persist.put("method", "changeProperty");
                persist.put("oldValue", new String(Hex.encodeHex(digester.digest(oldPassword.getBytes()))));
            }
            persist.put("newValue", new String(Hex.encodeHex(digester.digest(newPassword.getBytes()))));
            
            JSONObject commit = new JSONObject();
            commit.put("uuid", JSONObject.NULL);
            commit.put("method", "commit");
            
            JSONArray transaction = new JSONArray();
            transaction.put(begin);
            transaction.put(persist);
            transaction.put(commit);

            URI serverURI = new URI("http", null, 
                    serviceInfo.getServerAddress(), 
                    serviceInfo.getPort(),
                    serviceInfo.getPath() + 
                    "/" + ClientSideSessionUtils.REST_TAG + "/project/system", 
                    "currentRevision=" + getCurrentRevisionNumber(), null);
            HttpPost postRequest = new HttpPost(serverURI);
            postRequest.setEntity(new StringEntity(transaction.toString())); 
            postRequest.setHeader("Content-Type", "application/json");
            HttpUriRequest request = postRequest;
            JSONMessage result = client.execute(request, new JSONResponseHandler());
            if (result.getStatusCode() != 200) {
                logger.warn("Failed password change");
                if (result.getStatusCode() == 412) {
                    upf.createUserPrompter("The password you have entered is incorrect.", 
                            UserPromptType.MESSAGE, 
                            UserPromptOptions.OK, 
                            UserPromptResponse.OK, 
                            "OK", "OK").promptUser("");
                } else {
                    upf.createUserPrompter(
                            "Could not change the password due to the following: " + 
                            result.getBody() + " See logs for more details.", 
                            UserPromptType.MESSAGE, 
                            UserPromptOptions.OK, 
                            UserPromptResponse.OK, 
                            "OK", "OK").promptUser("");
                }
            } else {
                upf.createUserPrompter(
                        "Password successfully changed. Please log into open projects" +
                        " with your new password.", 
                        UserPromptType.MESSAGE, 
                        UserPromptOptions.OK, 
                        UserPromptResponse.OK, 
                        "OK", "OK").promptUser("");
            }
        } catch (AccessDeniedException ex) {
            logger.warn("Failed password change", ex);
            upf.createUserPrompter("The password you have entered is incorrect.", 
                    UserPromptType.MESSAGE, 
                    UserPromptOptions.OK, 
                    UserPromptResponse.OK, 
                    "OK", "OK").promptUser("");
        } catch (Exception ex) {
            logger.warn("Failed password change", ex);
            upf.createUserPrompter(
                    "Could not change the password due to the following: " + 
                    ex.getMessage() + " See logs for more details.", 
                    UserPromptType.MESSAGE, 
                    UserPromptOptions.OK, 
                    UserPromptResponse.OK, 
                    "OK", "OK").promptUser("");
        }
	}
	
	public void persistRevisionFromServer(int revisionNo, SPJSONMessageDecoder targetDecoder)
	throws IOException, URISyntaxException, SPPersistenceException, IllegalArgumentException {
	    persistRevisionFromServer(projectLocation, revisionNo, targetDecoder);
	}

	/**
     * Gets a list of DiffChunks representing the differences between the two revisions from the server.
     */
	public List<DiffChunk<DiffInfo>> getComparisonDiffChunks(int oldRevisionNo, int newRevisionNo) 
	throws IOException, URISyntaxException, JSONException, SPPersistenceException {
	    
        SPServerInfo serviceInfo = projectLocation.getServiceInfo();
        HttpClient httpClient = ClientSideSessionUtils.createHttpClient(serviceInfo, cookieStore);
        
        try {
            JSONMessage response = ClientSideSessionUtils.executeServerRequest(httpClient, projectLocation.getServiceInfo(),
                    "/" + ClientSideSessionUtils.REST_TAG + "/project/" + projectLocation.getUUID() + "/compare",
                    "versions=" + oldRevisionNo + ":" + newRevisionNo, 
                    new JSONResponseHandler());    
                                  
            return SimpleDiffChunkJSONConverter.decode(response.getBody());
            
        } finally {
            httpClient.getConnectionManager().shutdown();
        }
        
	}
    
    public static HttpClient createHttpClient(SPServerInfo serviceInfo) {
        return ClientSideSessionUtils.createHttpClient(serviceInfo, cookieStore);
    }
    
    public static void persistRevisionFromServer(ProjectLocation projectLocation, 
            int revisionNo, 
            SPJSONMessageDecoder decoder)
    throws IOException, URISyntaxException, SPPersistenceException, IllegalArgumentException {
        
        ClientSideSessionUtils.persistRevisionFromServer(projectLocation, revisionNo, decoder, cookieStore);
    }
	
	public static ProjectLocation createNewServerSession(SPServerInfo serviceInfo, String name, 
	        ArchitectSession session)
    throws URISyntaxException, ClientProtocolException, IOException, JSONException {
        return ClientSideSessionUtils.createNewServerSession(serviceInfo,
                name,
                cookieStore,
                new DefaultUserPrompterFactory());
    }
    
    public static List<ProjectLocation> getWorkspaceNames(SPServerInfo serviceInfo,
            UserPrompterFactory upf) 
    throws IOException, URISyntaxException, JSONException {
        return ClientSideSessionUtils.getWorkspaceNames(serviceInfo, cookieStore, upf);
    }
	
	public static void deleteServerWorkspace(ProjectLocation projectLocation, ArchitectSession session) throws URISyntaxException, ClientProtocolException, IOException {
    	
	    ClientSideSessionUtils.deleteServerWorkspace(projectLocation,
	            cookieStore, new DefaultUserPrompterFactory());
    }
    
    public ArchitectNetworkConflictResolver getUpdater() {
        return updater;
    }
	
	// ----------- Preferences accessors and mutators -----------
	// XXX Add different types, such as int or boolean, as needed

	/**
	 * Enters a double value as a preference for this server session.
	 * It will be able to be loaded by this local user in the future.
	 */
    public void putPref(String prefName, double pref) {
        prefs.putDouble(projectLocation.getUUID() + "." + prefName, pref);
    }    
    
    /**
     * Enters a String as a preference for this server session.
     * It will be able to be loaded by this local user in the future.
     */
    public void putPref(String prefName, String pref) {
        prefs.put(projectLocation.getUUID() + "." + prefName, pref);
    }
    
    /**
     * Retrieves a locally saved preference of type double.
     * @param prefName The name of the previously saved preference
     * @return The previously saved preference, or 0 if none exists yet
     */
    public double getPrefDouble(String prefName) {
        return getPrefDouble(prefName, 0);
    }
    
    /**
     * Retrieves a locally saved preference of type double.
     * @param prefName The name of the previously saved preference
     * @param def The value this function should return 
     * if no preference was previously saved
     */
    public double getPrefDouble(String prefName, double def) {
        return prefs.getDouble(projectLocation.getUUID() + "." + prefName, def);
    }
    
    /**
     * Retrieves a locally saved preference of type String.
     * @param prefName The name of the previously saved preference
     * @return The previously saved preference, or null if none exists yet
     */
    public String getPref(String prefName) {
        return getPref(prefName, null);
    }
    
    /**
     * Retrieves a locally saved preference of type String.
     * @param prefName The name of the previously saved preference
     * @param def The value this function should return 
     * if no preference was previously saved
     */
    public String getPref(String prefName, String def) {
        return prefs.get(projectLocation.getUUID() + "." + prefName, def);
    }

    public int getCurrentRevisionNumber() {
        return updater.getRevision();
    }
    
    /*
     * Do not remove this method without first changing the custom PlDotini in getDataSources().
     * Doing so will cause infinite recursion when accessing the SQLTypes.
     */
    @Override
    public List<UserDefinedSQLType> getSQLTypes() {
        // The following was my attempt to merge the snapshot and system types lists together
        // without making it O(mn), but the code is a bit lengthier than I'd like, so perhaps
        // the added complexity may not be worth it?
        SnapshotCollection collection = getWorkspace().getSnapshotCollection();
        
        List<UserDefinedSQLTypeSnapshot> typeSnapshots = 
            new ArrayList<UserDefinedSQLTypeSnapshot>(
                   collection.getChildren(UserDefinedSQLTypeSnapshot.class));
        List<UserDefinedSQLType> systemTypes = 
            new ArrayList<UserDefinedSQLType>(
                    getSystemWorkspace().getChildren(UserDefinedSQLType.class));
        
        // Remove domain snapshots from the list
        for (int i = typeSnapshots.size() - 1; i >= 0; i--) {
            UserDefinedSQLTypeSnapshot snapshot = typeSnapshots.get(i);
            if (snapshot.isDomainSnapshot()) {
                typeSnapshots.remove(i);
            }
        }
        
        // If there are no snapshots, just return the system types.
        if (typeSnapshots.size() == 0) return Collections.unmodifiableList(systemTypes);

        // Sort both lists by the UUIDs of the system types
        Collections.sort(typeSnapshots, new Comparator<UserDefinedSQLTypeSnapshot>() {
            public int compare(UserDefinedSQLTypeSnapshot o1, UserDefinedSQLTypeSnapshot o2) {
                return o1.getOriginalUUID().compareTo(o2.getOriginalUUID());
            }
        });
        Collections.sort(systemTypes, new SPObjectUUIDComparator<UserDefinedSQLType>());

        // Now go through the list of system types. If a snapshot type's
        // original UUID matches, then replace the system type with the snapshot.
        Iterator<UserDefinedSQLTypeSnapshot> snapshotIterator = typeSnapshots.iterator();
        UserDefinedSQLTypeSnapshot currentSnapshot = snapshotIterator.next();
        
        for (int i = 0; i < systemTypes.size() ; i++) {
            UserDefinedSQLType type = systemTypes.get(i);
            int compareTo = currentSnapshot.getOriginalUUID().compareTo(type.getUUID());
            if (compareTo <= 0) {
                if (compareTo == 0) {
                    systemTypes.set(i, currentSnapshot.getSPObject());
                } else {
                    systemTypes.add(i, currentSnapshot.getSPObject());
                }
                if (snapshotIterator.hasNext() && i != systemTypes.size() - 1) {
                    currentSnapshot = snapshotIterator.next();
                } else {
                    break;
                }
            }
        }
        
        // If we've gone through all the system types, then append the remaining snapshot types
        while (snapshotIterator.hasNext()) {
            currentSnapshot = snapshotIterator.next();
            systemTypes.add(currentSnapshot.getSPObject());
        }
        
        return Collections.unmodifiableList(systemTypes);
    }

    /**
     * Returns the {@link List} of {@link UserDefinedSQLType}s in this session
     * that are children of a {@link DomainCategory}. Each domain in the system
     * will be in the list once as either the domain that is in the system
     * workspace or a snapshot of the domain that is in the current project. UDTs
     * that are children of a category are defined as domains instead of types.
     */
    @Override
    public List<UserDefinedSQLType> getDomains() {
        // The following was my attempt to merge the snapshot and system category lists together
        // without making it O(nm), but the code is a bit lengthier than I'd like, so perhaps
        // the added complexity may not be worth it?
        SnapshotCollection collection = getWorkspace().getSnapshotCollection();
        
        List<UserDefinedSQLTypeSnapshot> typeSnapshots = 
            new ArrayList<UserDefinedSQLTypeSnapshot>(collection.getChildren(UserDefinedSQLTypeSnapshot.class));
        List<DomainCategory> systemCategories = 
            new ArrayList<DomainCategory>(getSystemWorkspace().getChildren(DomainCategory.class));
        List<UserDefinedSQLTypeSnapshot> domainSnapshots = new ArrayList<UserDefinedSQLTypeSnapshot>();
        for (UserDefinedSQLTypeSnapshot udtSnapshot : typeSnapshots) {
            if (udtSnapshot.isDomainSnapshot()) {
                domainSnapshots.add(udtSnapshot);
            }
        }
        List<UserDefinedSQLType> systemDomains = new ArrayList<UserDefinedSQLType>();
        for (DomainCategory category : systemCategories) {
            systemDomains.addAll(category.getChildren(UserDefinedSQLType.class));
        }
        
        // If there are no snapshots, just return the system categories.
        if (domainSnapshots.size() == 0) return Collections.unmodifiableList(systemDomains);
        
        // Sort both lists by the UUIDs of the system categories
        Collections.sort(domainSnapshots, new Comparator<UserDefinedSQLTypeSnapshot>() {
            public int compare(UserDefinedSQLTypeSnapshot o1, UserDefinedSQLTypeSnapshot o2) {
                return o1.getOriginalUUID().compareTo(o2.getOriginalUUID());
            }
        });
        Collections.sort(systemDomains, new SPObjectUUIDComparator<UserDefinedSQLType>());

        // Now go through the list of system categories. If a snapshot category's
        // original UUID matches, then replace the system category with the snapshot.
        Iterator<UserDefinedSQLTypeSnapshot> snapshotIterator = domainSnapshots.iterator();
        UserDefinedSQLTypeSnapshot currentSnapshot = snapshotIterator.next();
        
        for (int i = 0; i < systemDomains.size() ; i++) {
            UserDefinedSQLType type = systemDomains.get(i);
            int compareTo = currentSnapshot.getOriginalUUID().compareTo(type.getUUID());
            if (compareTo <= 0) {
                if (compareTo == 0) {
                    systemDomains.set(i, currentSnapshot.getSPObject());
                } else {
                    systemDomains.add(i, currentSnapshot.getSPObject());
                }
                if (snapshotIterator.hasNext() && i != systemDomains.size() - 1) {
                    currentSnapshot = snapshotIterator.next();
                } else {
                    break;
                }
            }
        }
        
        // If we've gone through all the system types, then append the remaining snapshot categories
        while (snapshotIterator.hasNext()) {
            currentSnapshot = snapshotIterator.next();
            systemDomains.add(currentSnapshot.getSPObject());
        }
        
        return Collections.unmodifiableList(systemDomains);
    }

    @Override
    public ArchitectSwingProject getWorkspace() {
        return (ArchitectSwingProject) super.getWorkspace();
    }
    
    /**
     * Finds the UDT that has the same uuid as the given snapshot's
     * {@link SPObjectSnapshot#getOriginalUUID()} method. The snapshot given
     * cannot be null. Returns null if no type is found.
     */
    public UserDefinedSQLType findSystemTypeFromSnapshot(@Nonnull SPObjectSnapshot<?> snapshot) {
        for (UserDefinedSQLType systemType : getSystemWorkspace().getSqlTypes()) {
            if (systemType.getUUID().equals(snapshot.getOriginalUUID())) {
                return systemType;
            }
        }
        for (DomainCategory category : getSystemWorkspace().getDomainCategories()) {
            for (UserDefinedSQLType systemType : category.getChildren(UserDefinedSQLType.class)) {
                if (systemType.getUUID().equals(snapshot.getOriginalUUID())) {
                    return systemType;
                }
            }
        }
        return null;
    }

    /**
     * Returns an action that will update the snapshot object in the given
     * snapshot to be the same as the type the snapshot maps to in the system
     * workspace.
     * 
     * @param snapshot
     *            The snapshot to update. This currently only works for
     *            snapshots of UserDefinedSQLTypes but will be extended to
     *            others in the future.
     */
    public Runnable createUpdateSnapshotRunnable(final SPObjectSnapshot<?> snapshot) {
        return new Runnable() {

            @Override
            public void run() {
                if (snapshot.getSPObject() instanceof DomainCategory) {
                    for (DomainCategory category : getSystemWorkspace().getDomainCategories()) {
                        if (category.getUUID().equals(snapshot.getOriginalUUID())) {
                            ((DomainCategory) snapshot.getSPObject()).updateToMatch(category);
                            snapshot.setObsolete(false);
                            return;
                        }
                    }
                }
                
                if (!(snapshot.getSPObject() instanceof UserDefinedSQLType)) return;
                UserDefinedSQLType snapshotType = (UserDefinedSQLType) snapshot.getSPObject();
                UserDefinedSQLType systemType = findSystemTypeFromSnapshot(snapshot);
                if (systemType == null) return;
                
                snapshotType.updateToMatch(systemType);
                snapshot.setObsolete(false);
            }
        };
    }
} public interface ArchitectLayout {

	/**
	 * Sets up the layout algoritm.  You have to call this before attempting
     * to use an ArchitectLayout instance to perform a layout.
	 * 
     * TODO change the Rectangle into a point.  This should indicate where to start
     * but the layout should determine dimensions 
     * 
	 * @param nodes  The list of entities to be placed on  
	 * @param edges 
	 *             List of LayoutEdges
     * @param frame
	 */
	public void setup(Collection<? extends LayoutNode> nodes, Collection<? extends LayoutEdge> edges, Rectangle frame);
	
	public Dimension getNewArea(List<? extends LayoutNode> nodes);
	
	/**
	 * Interrupts the algorithm and node bounds in a consistent state.
	 */
	public void done();
	
	/**
	 * Returns true iff the layout is finished.
	 * 
	 * @return true if the layout algorithm is finished with the layout, false otherwise
	 */
	public boolean isDone();
	
	/**
	 * Updates all the node locations to correspond with the positions they should
     * have in the next frame.
	 */
	public void nextFrame();

} public class FruchtermanReingoldForceLayout extends AbstractLayout {
	private static final Logger logger = Logger.getLogger(FruchtermanReingoldForceLayout.class);

	private ArrayList<LayoutNode> nodes;
    private ArrayList<LayoutNode> orphanedTables = new ArrayList<LayoutNode>();
	private ArrayList<LayoutEdge> edges;

	/**
	 * The radius that is kept clear
	 */
	private double k;

	/**
	 * The maximum movement 
	 */
	private double temp;

	/**
	 * Spacing multiplier
	 */
	private static final double SPACING_MULTIPLIER = 2;
	
	/**
	 *  The number of frames in a row that are considered stopped
	 */
	private int stoppedFrames;

	/**
	 * The amount of movement at the begining of the frame
	 * or away from the edge of the playpen
	 */
	private int  baseLineJitter;
	
	/**
	 * Used to be able to stop the algoithm prematurely
	 */
	private boolean overrideDone;

    private int frameNum = 0;
    
    private static final int ORPHAN_BUFFER = 50;
    
	public void setup(Collection<? extends LayoutNode> nodes, Collection<? extends LayoutEdge> edges, Rectangle frame) {
		
	    this.frame = new Rectangle(frame);
              
	    //nodes.removeAll(noRelationTables);
		this.nodes = new ArrayList<LayoutNode>(nodes);
        
        for (LayoutNode tp: nodes) {
            if (tp.getOutboundEdges().size() == 0 && tp.getInboundEdges().size() == 0){
                orphanedTables.add(tp);
            }
        }
        
        this.nodes.removeAll(orphanedTables);
        
		this.edges = new ArrayList<LayoutEdge>(edges);

		
		baseLineJitter =10;
		temp = 1000*nodes.size();
		
		stoppedFrames =0;
		overrideDone = false;
		k = getEmptyRadius();
	}

	/**
	 * get the magnitude of a point
	 * @param p
	 * @return the magnitude
	 */
	public double magnitude(Point p) {
		return p.distance(0, 0);
	}

	/**
	 * Checks and sees if the program stops
	 */
	public boolean isDone() {
		if (stoppedFrames > 5 || (nodes.size() + orphanedTables.size() <2) || overrideDone) {

			return true;
		}
		return false;
	}

	/**
	 * Performs the next step of the spring layout
	 */
	public void nextFrame() {
        
        if (frameNum == 0) {
            ArchitectGridLayout gl = new ArchitectGridLayout();
            gl.setup(orphanedTables, Collections.EMPTY_LIST, frame);
            gl.done();
            
            int maxy = 0;
            for (LayoutNode tp : orphanedTables) {
                maxy = Math.max(tp.getY() + tp.getHeight(), maxy);
            }
            int orphanStartY = frame.height - maxy;
            logger.debug("max y is "+maxy+". orphanStartY is "+orphanStartY);
            for (LayoutNode tp : orphanedTables) {
                tp.setLocation(tp.getX(), tp.getY() + orphanStartY);
            }
            frame.height -= maxy + ORPHAN_BUFFER;
        }
		
		HashMap<LayoutNode, Point> displacement;
		displacement = new HashMap<LayoutNode, Point>();
		
		for (LayoutNode tp : this.nodes) {
			displacement.put(tp, new Point((int)Math.round(Math.random()*baseLineJitter-baseLineJitter/2),+(int)Math.round(Math.random()*baseLineJitter-baseLineJitter/2)));
		}
		// Calculate repulsive forces
		if (nodes != null && !isDone()) {
			for (int ii = 0; ii < nodes.size(); ii++) {
				LayoutNode v = nodes.get(ii);
				Point disp = displacement.get(v);
				for (int jj = 0; jj < nodes.size(); jj++) {
					LayoutNode u = nodes.get(jj);
					if (u == v) continue;
                    
					while (v.getLocation().equals(u.getLocation())) {
					    v.setLocation((int) Math.round(Math.random()*5-3),
					                  (int) Math.round(Math.random()*5-3));
					}
					
					Point delta = displacementBetween(u, v);
					
					if (delta.distance(0,0) < 5*k) {
					    disp.translate(
					      (int) Math.round(delta.x / magnitude(delta) * repulsiveForce(magnitude(delta), u, v, k)),
					      (int) Math.round(delta.y / magnitude(delta) * repulsiveForce(magnitude(delta), u, v, k)));
					}

				}

			}
			// calculate Attractive force

			for (LayoutEdge e : edges) {
            
				LayoutNode v = e.getTailNode();
				LayoutNode u = e.getHeadNode();
				Point delta = displacementBetween(u, v);
                                 
				Point vDisp = displacement.get(v);
				if (vDisp == null) {
					break;
				}
				Point uDisp = displacement.get(u);
				if (uDisp == null) {
					break;
				}
				if(uDisp.equals(vDisp)) {
                    vDisp.translate((int) (Math.random() + 0.5), (int) (Math.random() + 0.5));
				}
				
				vDisp.translate(
                        -(int) Math.round(0.5 * (delta.x / magnitude(delta) * attractiveForce(delta, k))),
                        -(int) Math.round(0.5 * (delta.y / magnitude(delta) * attractiveForce(delta, k))));

				uDisp.translate(
                        (int) Math.round(0.5 * (delta.x / magnitude(delta) * attractiveForce(delta, k))),
                        (int) Math.round(0.5 * (delta.y / magnitude(delta) * attractiveForce(delta, k))));
			}

			boolean done = true;
			// add the forces to the current location
			for (LayoutNode v : nodes) {
				Point pos = (Point) v.getLocation().clone();
				Point disp = displacement.get(v);
				pos.translate(
                        (int) Math.round(disp.x / magnitude(disp) * Math.min(magnitude(disp), getTemp())),
                        (int) Math.round(disp.y / magnitude(disp) * Math.min(magnitude(disp), getTemp())));

                if (logger.isDebugEnabled()) {
                    logger.debug("Unmodified move x:"+pos.x+", y:"+pos.y);
                }
                
                int xJitter = (int) Math.round(Math.random()*baseLineJitter);
                int yJitter = (int) Math.round(Math.random()*baseLineJitter);
                pos.x = Math.min(Math.max(frame.x + xJitter, pos.x),
                                 getRightBoundary() - v.getWidth() - xJitter);
				pos.y = Math.min(Math.max(frame.y + yJitter, pos.y),
                                 getLowerBoundary() - v.getHeight() - yJitter);
                
                if (logger.isDebugEnabled()) {
                    logger.debug("Moving table "+v+" to position "+pos);
                }

                if (pos.distance(v.getLocation()) > 2) {
					done = false;
				}
				v.setLocation(pos);
			}
			if (done) {
				stoppedFrames++;
			}
		}
		this.cool();
        frameNum++;
	}

    private Point displacementBetween(LayoutNode u, LayoutNode v) {
        Point dist = null;
     
        if (dist == null) {
            dist = new Point(
                    (v.getLocation().x + v.getWidth()/2) - (u.getLocation().x + u.getWidth()/2),
                    (v.getLocation().y + v.getHeight()/2) - (u.getLocation().y + u.getHeight()/2));
        }
        return dist;
    }
    
	/**
	 * Calculate the attractive force
	 * @param distance The distance between two nodes
	 * @param emptyRadius  The radius we want to keep clear
	 * @return the force between the two nodes
	 */
	protected double attractiveForce(Point distance, double emptyRadius) {
		double force;
		force =  (distance.x *distance.x + distance.y*distance.y) / emptyRadius;
		

		return force;
	}

	/**
	 * Calculate the Repulsive force
	 * @param distance The distance between two nodes
	 * @param emptyRadius  The radius we want to keep clear
	 * @return the force between the two nodes
	 */
	protected double repulsiveForce(double distance,LayoutNode u, LayoutNode v, double emptyRadius) {
        final double baseForce = .1;
        final double tableSizes = u.getHeight() + u.getWidth() + v.getHeight() + v.getWidth();
		double force;
		force = baseForce * tableSizes *( emptyRadius * emptyRadius ) / (distance * distance );

		return force;
	}
 

	/**
	 * The clear radius between two nodes
	 * @return
	 */
	public double getEmptyRadius() {
			double radius =0;
			for (LayoutNode tp : nodes) {
				Rectangle b = tp.getBounds();
				radius +=b.height;
				radius +=b.width;
			}
            radius = radius/(nodes.size()); 
			return radius*SPACING_MULTIPLIER;
	}

	public void done() {
		overrideDone = true;
	}

	/**
	 * Cool the temperature
	 *
	 */
	public void cool() {	
		temp = temp/1.1;
	}
	
	public ArrayList<LayoutEdge> getEdges() {
		return edges;
	}

	public double getK() {
		return k;
	}

	public ArrayList<LayoutNode> getNodes() {
		return nodes;
	}

	public double getTemp() {
		return temp;
	}

	public int getH() {
		return frame.height;
	}

	public int getRightBoundary(){
		return frame.x+frame.width;
		
	}
	
	public int getLowerBoundary(){
		return frame.y+frame.height;
	}
	
	public int getW() {
		return frame.width;
	}

}    public interface CancelableListener {

	public void cancel();

} public class EditSelectedAction extends AbstractArchitectAction implements SelectionListener {
    private static final Logger logger = Logger.getLogger(EditSelectedAction.class);
    
    public EditSelectedAction(ArchitectFrame frame) {
        super(frame, Messages.getString("EditSelectedAction.name"), Messages.getString("EditSelectedAction.description"), "edit_selected"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        
        frame.addSelectionListener(this);
        setupAction(getPlaypen().getSelectedItems());
    }

    public void actionPerformed(ActionEvent e) {
        
        // Find all selected tables and relationships
        List<PlayPenComponent> selection = getPlaypen().getSelectedItems();
        
        boolean tablesSelected = false;
        boolean relationshipsSelected = false;
        boolean columnsSelected = false;
        boolean labelsSelected = false;
        
        for (PlayPenComponent ppc : selection) {
            if (ppc instanceof TablePane) {
                tablesSelected = true;
                
                TablePane tp = (TablePane) ppc;
                if (tp.getSelectedItemIndex() >= 0) {
                    columnsSelected = true;
                }
            } else if (ppc instanceof Relationship) {
                relationshipsSelected = true;
            } else if (ppc instanceof PlayPenLabel) {
                labelsSelected = true;
            }
        }

        if (columnsSelected && !relationshipsSelected && !labelsSelected) {
            // note: we expect tables to be selected too in this case, but we ignore that
            // and let the column selections take precedence
            frame.getEditColumnAction().actionPerformed(e);
        } else if (tablesSelected && !relationshipsSelected && !labelsSelected) {
            frame.getEditTableAction().actionPerformed(e);
        } else if (relationshipsSelected && !tablesSelected && !labelsSelected) {
            frame.getEditRelationshipAction().actionPerformed(e);
        } else if (labelsSelected && !tablesSelected && !relationshipsSelected) {
            frame.getEditLabelAction().actionPerformed(e);
        } else if (selection.size() > 0) {
            JOptionPane.showMessageDialog(frame, Messages.getString("EditSelectedAction.multipleItemsSelected")); //$NON-NLS-1$
        } else {
            JOptionPane.showMessageDialog(frame, Messages.getString("EditSelectedAction.noItemsSelected")); //$NON-NLS-1$
        }
    }

    public void itemSelected(SelectionEvent e) {
        setupAction(getPlaypen().getSelectedItems());
    }

    public void itemDeselected(SelectionEvent e) {
        setupAction(getPlaypen().getSelectedItems());
    }


    /**
     * Updates the tooltip and enabledness of this action based on how
     * many items are in the selection list.  If there is only one
     * selected item, tries to put its name in the tooltip too!
     */
    private void setupAction(List<PlayPenComponent> selectedItems) {
        String description;
        if (selectedItems.size() == 0) {
            setEnabled(false);
            description = Messages.getString("EditSelectedAction.editSelected"); //$NON-NLS-1$
        } else if (selectedItems.size() == 1) {
            Selectable item = (Selectable) selectedItems.get(0);
            setEnabled(true);
            String name = Messages.getString("EditSelectedAction.selected"); //$NON-NLS-1$
            if (item instanceof TablePane) {
                TablePane tp = (TablePane) item;
                if (tp.getSelectedItemIndex() >= 0) {
                    try {
                        List<SQLColumn> selectedColumns = tp.getSelectedItems();
                        if (selectedColumns.size() > 1) {
                            name = Messages.getString("EditSelectedAction.numberOfitems", String.valueOf(selectedColumns.size())); //$NON-NLS-1$
                        } else {
                            name = tp.getModel().getColumn(tp.getSelectedItemIndex()).getName();
                        }
                    } catch (SQLObjectException ex) {
                        logger.error("Couldn't get selected column name", ex); //$NON-NLS-1$
                    }
                } else {
                    name = tp.getModel().getName();
                }
            } else if (item instanceof Relationship) {
                name = ((Relationship) item).getModel().getName();
            }
            description = Messages.getString("EditSelectedAction.editItem", name); //$NON-NLS-1$
        } else {
            setEnabled(true);
            int numSelectedItems =0;
            for (Object item : selectedItems) {
                numSelectedItems++;
                if (item instanceof TablePane) {
                    // Because the table pane is already counted we need to add one less
                    // than the columns unless there are no columns selected.  Then
                    // We need to add 0
                    numSelectedItems += Math.max(((TablePane) item).getSelectedItems().size()-1, 0);
                }
            }
            description = Messages.getString("EditSelectedAction.editNumberOfItems", String.valueOf(numSelectedItems)); //$NON-NLS-1$
        }
        putValue(SHORT_DESCRIPTION, description + Messages.getString("EditSelectedAction.shortcut")); //$NON-NLS-1$
    }
} public class EditSelectedIndexAction extends EditIndexAction {

    private static final Logger logger = Logger.getLogger(EditSelectedIndexAction.class);
    
    /**
     * Creates a new instance of this action which, when invoked, will edit the
     * currently-selected SQLIndex.  Normally only one instance of this action
     * will be created in a particular ArchitectSession.  To create an action that
     * edits a specific index, see {@link EditSpecificIndexAction}.
     * 
     * @param frame The ArchitectFrame to which this action belongs.
     */
    public EditSelectedIndexAction(ArchitectFrame frame) {
        super(frame, Messages.getString("EditSelectedIndexAction.name"), Messages.getString("EditSelectedIndexAction.description"), "edit_index"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    }

    public void actionPerformed(ActionEvent evt) {
        TreePath [] selections = getSession().getDBTree().getSelectionPaths();
        logger.debug("selections length is: " + selections.length); //$NON-NLS-1$
        if (selections.length != 1) {
            JOptionPane.showMessageDialog(frame, Messages.getString("EditSelectedIndexAction.instructions")); //$NON-NLS-1$
        } else {
            TreePath tp = selections[0];
            SQLObject so = (SQLObject) tp.getLastPathComponent();
            SQLIndex si = null;

            if (so instanceof SQLIndex) {
                logger.debug("user clicked on index, so we shall try to edit the index properties."); //$NON-NLS-1$
                si = (SQLIndex) so;
                try {
                    makeDialog(si);
                } catch (SQLObjectException e) {
                    throw new SQLObjectRuntimeException(e);
                } 
            } else {
                JOptionPane.showMessageDialog(frame, Messages.getString("EditSelectedIndexAction.instructions")); //$NON-NLS-1$
            }
        }
    }
} public class ExportDDLAction extends AbstractArchitectAction {

    private static final Logger logger = Logger.getLogger(ExportDDLAction.class);

	private static final String GENDDL_WARNINGS_EXPLANATION =
        Messages.getString("ExportDDLAction.errorsInstructions"); //$NON-NLS-1$
	
	private JDialog d;

	public ExportDDLAction(final ArchitectFrame frame) {
		super(frame, Messages.getString("ExportDDLAction.name"), Messages.getString("ExportDDLAction.description"), "fwdSQL"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
	}

    public void actionPerformed(ActionEvent e) {
        final DDLExportPanel ddlPanel = new DDLExportPanel(getSession());

        Callable<Boolean> okCall, cancelCall;
        okCall = new Callable<Boolean>() {
            public Boolean call() {
                if (ddlPanel.applyChanges()) {

                    DDLGenerator ddlg = ddlPanel.getGenerator();
                    ddlg.setTargetSchema(ddlPanel.getSchemaField().getText());

                    checkErrorsAndGenerateDDL(ddlg);

                }
                return Boolean.FALSE;
            }

            /**
             * This method will run the known critics over the target database
             * that we want to generate a DDL script for and display a dialog
             * containing errors if any are found. The user will then have the
             * choice to fix their data model or continue on ignoring the
             * current set of errors.
             * <p>
             * This method will also generate the DDL script using the
             * generateAndDisplayDDL method.
             */
            private void checkErrorsAndGenerateDDL(final DDLGenerator ddlg) {
                List<Criticism> criticisms = getSession().getWorkspace().getCriticManager().
                    criticize(ddlg.getClass());
                if (criticisms.isEmpty()) {
                    try {
                        generateAndDisplayDDL(ddlPanel, ddlg);
                    } catch (Exception ex) {
                        ASUtils.showExceptionDialog
                        (getSession(),
                         Messages.getString("ExportDDLAction.errorGeneratingDDLScript"), ex); //$NON-NLS-1$
                    }
                } else {
                    //build warning dialog
                    final JDialog warningDialog = new JDialog(frame);
                    JPanel mainPanel = new JPanel();
                    DefaultFormBuilder builder = new DefaultFormBuilder(new FormLayout("pref:grow"), mainPanel);
                    builder.setDefaultDialogBorder();
                    JTextArea explanation = new JTextArea(GENDDL_WARNINGS_EXPLANATION, 8, 60);
                    explanation.setLineWrap(true);
                    explanation.setWrapStyleWord(true);
                    explanation.setEditable(false);
                    explanation.setBackground(mainPanel.getBackground());
                    explanation.setPreferredSize(new Dimension(0, 0));
                    builder.append(explanation);
                    
                    builder.appendRow("10dlu");
                    builder.nextLine();
                    
                    builder.appendRow("fill:pref:grow");
                    builder.nextLine();
                    
                    final CriticismBucket bucket = new CriticismBucket();
                    bucket.updateCriticismsToMatch(criticisms);
                    JTable errorTable = CriticSwingUtil.createCriticTable(getSession(), bucket);
                    builder.append(new JScrollPane(errorTable));
                    builder.nextLine();
                    
                    JButton quickFixButton = new JButton(
                            new AbstractAction(Messages.getString("ExportDDLAction.quickFixAllOption")) {  //$NON-NLS-1$
                        public void actionPerformed(ActionEvent e) {
                            warningDialog.dispose();
                            for (Criticism criticism : bucket.getCriticisms()) {
                                if (!criticism.getFixes().isEmpty()) {
                                    for (CriticFix fix : criticism.getFixes()) {
                                        if (fix.getFixType().equals(FixType.QUICK_FIX)) {
                                            fix.apply();
                                            //applying the first quick fix each time as there is no 
                                            //decision what to apply by the user for this case
                                            break;
                                        }
                                    }
                                }
                            }
                            checkErrorsAndGenerateDDL(ddlg);
                        }
                    });
                    JButton ignoreButton = new JButton(new AbstractAction(
                            Messages.getString("ExportDDLAction.ignoreWarningsOption")) { //$NON-NLS-1$
                        public void actionPerformed(ActionEvent e) {
                            warningDialog.dispose();
                            try {
                                generateAndDisplayDDL(ddlPanel, ddlg);
                            } catch (Exception ex) {
                                ASUtils.showExceptionDialog
                                (getSession(),
                                 Messages.getString("ExportDDLAction.errorGeneratingDDLScript"), ex); //$NON-NLS-1$
                            }                        }
                    });
                    JButton cancelButton = new JButton(new AbstractAction(
                            Messages.getString("ExportDDLAction.cancelOption")) { //$NON-NLS-1$
                        public void actionPerformed(ActionEvent e) {
                            //just dispose of the dialog and end this.
                            warningDialog.dispose();
                        }
                    });
                    JButton recheckButton = new JButton(new AbstractAction(
                            Messages.getString("ExportDDLAction.recheckOption")) { //$NON-NLS-1$
                        public void actionPerformed(ActionEvent e) {
                            warningDialog.dispose();
                            checkErrorsAndGenerateDDL(ddlg);
                        }
                    });
                    
                    ButtonBarBuilder buttonBar = new ButtonBarBuilder();
                    buttonBar.addGlue();
                    buttonBar.addGriddedButtons(new JButton[] {quickFixButton, 
                            ignoreButton, cancelButton, recheckButton});
                    
                    builder.append(buttonBar.getPanel());
                    warningDialog.add(mainPanel);
                    
                    warningDialog.pack();
                    TableUtils.fitColumnWidths(errorTable, 10);
                    errorTable.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
                    warningDialog.setLocationRelativeTo(frame);
                    warningDialog.setVisible(true);
                }
            }

            /**
             * This method is used for generating and displaying the DDL script
             * for the current target database using the given DDL generator.
             */
            private void generateAndDisplayDDL(final DDLExportPanel ddlPanel, DDLGenerator ddlg) throws SQLException,
            SQLObjectException {
                ddlg.generateDDLScript(getSession(), getSession().getTargetDatabase().getTables());

                SQLDatabase ppdb = new SQLDatabase(ddlPanel.getTargetDB());
                SQLScriptDialog ssd =
                    new SQLScriptDialog(d, Messages.getString("ExportDDLAction.previewSQLScriptDialogTitle"), "", false, //$NON-NLS-1$ //$NON-NLS-2$
                            ddlg,
                            ppdb.getDataSource(),
                            true,
                            getSession());
                SPSwingWorker scriptWorker = ssd.getExecuteTask();
                ConflictFinderProcess cfp = new ConflictFinderProcess(ssd, ppdb, ddlg, ddlg.getDdlStatements(), getSession());
                ConflictResolverProcess crp = new ConflictResolverProcess(ssd, cfp, getSession());
                cfp.setNextProcess(crp);
                crp.setNextProcess(scriptWorker);
                ssd.setExecuteTask(cfp);
                ssd.setVisible(true);
            }
        };

        cancelCall = new Callable<Boolean>() {
            public Boolean call() {
                ddlPanel.discardChanges();
                return Boolean.TRUE;
            }
        };
        d = DataEntryPanelBuilder.createDataEntryPanelDialog(
                ddlPanel,
                frame,
                Messages.getString("ExportDDLAction.forwardEngineerSQLDialogTitle"), Messages.getString("ExportDDLAction.okOption"), //$NON-NLS-1$ //$NON-NLS-2$
                okCall, cancelCall);

        d.pack();
        d.setLocationRelativeTo(frame);
        d.setVisible(true);
    }

	/**
	 * The ConflictFinderProcess uses a ConflictResolver (which it monitors with
	 * a progress bar) to locate objects in the target database which need to be
	 * removed before a set of DDL statements can be executed in it.
	 *
	 * @author fuerth
	 */
	public class ConflictFinderProcess extends SPSwingWorker {

		JDialog parentDialog;
		SQLDatabase target;
		DDLGenerator ddlg;
		List<DDLStatement> statements;


		/**
		 * This Conflict Resolver is created and populated by the run() method.
		 */
		ConflictResolver cr;

		/**
		 * If something goes wrong in the run() method, the runFinished() method will
		 * be given a message to display in this string.
		 */
		String errorMessage;

		/**
		 * If something throws an exception in the run() method, it is saved here and
		 * displayed in runFinished().
		 */
		Throwable error;
		private boolean shouldDropConflicts;

		/**
		 * @param parentDialog The JDialog we're doing this in.
		 * @param target The target database (where to search for the conflicts).
		 * @param ddlg The DDL Generator that we're using.
		 * @param statements The Liust of DDLStatement
		 * @param session  The ArchitectSwingSession
		 * @throws SQLObjectException If there is a problem connecting to the target database
		 */
		public ConflictFinderProcess(JDialog parentDialog, SQLDatabase target,
				DDLGenerator ddlg, List<DDLStatement> statements, ArchitectSwingSession session)
			throws SQLObjectException {
			super(session);
			this.parentDialog = parentDialog;
			this.target = target;
			this.ddlg = ddlg;
			this.statements = statements;

			cr = new ConflictResolver(target, ddlg, statements);
		}

		/**
		 * @return True if and only if the user has asked for the conflicts to
		 *         be deleted.
		 */
		public boolean doesUserWantToDropConflicts() {
			return shouldDropConflicts;
		}

		/**
		 * This method is called on its own thread (not the AWT event dispatch
		 * thread). It will take a while.
		 */
		public void doStuff() {

			if (this.isCancelled()) return;
            
            // First, test if it's possible to connect to the target database
            Connection con = null;
            try {
                con = target.getConnection();
            } catch (Exception ex) {
                error = ex;
                errorMessage = Messages.getString("ExportDDLAction.failedToConnectToDb"); //$NON-NLS-1$
                return;
            } finally {
                if (con != null) {
                    try {
                        con.close();
                    } catch (SQLException ex) {
                        logger.error("Failed to close connection. This exception is getting squashed:", ex); //$NON-NLS-1$
                    }
                }
            }
            
            // Now do the actual work
			try {
				cr.findConflicting();
			} catch (Exception ex) {
				error = ex;
				errorMessage = Messages.getString("ExportDDLAction.unexpectedException"); //$NON-NLS-1$
				logger.error("Unexpected exception setting up DDL generation", ex); //$NON-NLS-1$
			}
		}

		/**
		 * After the doStuff() method is done, this method will be invoked on the AWT event
		 * dispatch thread.
		 */
		public void cleanup() {
			if (!SwingUtilities.isEventDispatchThread()) {
				logger.error("runFinished is running on the wrong thread!"); //$NON-NLS-1$
			}
			if (errorMessage != null) {
                if (error != null) {
                    ASUtils.showExceptionDialogNoReport(parentDialog, errorMessage, error);
                } else {
                    JOptionPane.showMessageDialog(parentDialog, errorMessage, Messages.getString("ExportDDLAction.errorMessageDialogTitle"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
                }
			} else if (!cr.isEmpty()) {
				Object[] messages = new Object[3];
				messages[0] = Messages.getString("ExportDDLAction.conflictingObjectsInDatabase"); //$NON-NLS-1$
				JTextArea conflictsPane = new JTextArea(cr.toConflictTree());
				conflictsPane.setRows(15);
				conflictsPane.setEditable(false);
				messages[1] = new JScrollPane(conflictsPane);
				messages[2] = Messages.getString("ExportDDLAction.dropConflictingObjectsConfirmation"); //$NON-NLS-1$
				int choice = JOptionPane.showConfirmDialog(
						parentDialog,
						messages,
						Messages.getString("ExportDDLAction.conflictingObjectsInDatabaseDialogTitle"), //$NON-NLS-1$
						JOptionPane.YES_NO_CANCEL_OPTION);
				if (choice == JOptionPane.YES_OPTION) {
					shouldDropConflicts = true;
				} else if (choice == JOptionPane.NO_OPTION) {
					shouldDropConflicts = false;
				} else if (choice == JOptionPane.CANCEL_OPTION) {
					shouldDropConflicts = false;
					this.setCancelled(true);
				}
			}

		}

		public ConflictResolver getConflictResolver() {
			return cr;
		}


		@Override
		protected Integer getJobSizeImpl() {
			return cr.getJobSize();
		}

		@Override
		protected String getMessageImpl() {
			return cr.getMessage();
		}

		@Override
		protected int getProgressImpl() {
			return cr.getProgress();
		}

		@Override
		protected boolean hasStartedImpl() {
			return cr.hasStarted();
		}

		@Override
		protected boolean isFinishedImpl() {
			return cr.isFinished();
		}


	}

	/**
	 * The ConflictResolverProcess grabs a conflict resolver from the conflict
	 * finder process, checks if the user said to delete the conflicts, then
	 * asks it to remove the conflicting items while monitoring the progress.
	 *
	 * @author fuerth
	 * @version $Id$
	 */
	public class ConflictResolverProcess extends SPSwingWorker {

		private JDialog parentDialog;
		private ConflictFinderProcess conflictFinder;

		private ConflictResolver cr;

		private String errorMessage;
		private Exception error;

		/**
		 * @param d The dialog we anchor popup messages to
		 * @param cfp The conflict finder we extract the conflict list from
		 * @param session The ArchitectSwingSession
		 */
		public ConflictResolverProcess(JDialog d, ConflictFinderProcess cfp, ArchitectSwingSession session) {
			super(session);
            this.parentDialog = d;
			this.conflictFinder = cfp;
		}

		public void doStuff() {
			if (isCancelled())
				return;
			if (conflictFinder.doesUserWantToDropConflicts()) {
				cr = conflictFinder.getConflictResolver();
				cr.aboutToCallDropConflicting();
				try {
					cr.dropConflicting();
				} catch (Exception ex) {
					logger.error("Error while dropping conflicting objects", ex); //$NON-NLS-1$
					errorMessage = Messages.getString("ExportDDLAction.errorDroppingConflictingObjects")+ex.getMessage(); //$NON-NLS-1$
				}
			}
		}

		/**
		 * Displays error messages or invokes the next process in the chain on a new
		 * thread. The run method asks swing to invoke this method on the event dispatch
		 * thread after it's done.
		 */
		public void cleanup() {
			if (errorMessage != null) {
				ASUtils.showExceptionDialogNoReport(parentDialog,
                    Messages.getString("ExportDDLAction.errorDroppingConflictingObjects")+errorMessage, error); //$NON-NLS-1$
				setCancelled(true);
			}
		}

		@Override
		protected Integer getJobSizeImpl() {
			return cr.getJobSize();
		}

		@Override
		protected String getMessageImpl() {
			return cr.getMessage();
		}

		@Override
		protected int getProgressImpl() {
			return cr.getProgress();
		}

		@Override
		protected boolean hasStartedImpl() {
			return cr.hasStarted();
		}

		@Override
		protected boolean isFinishedImpl() {
			return cr.isFinished();
		}


	}


} public class KettleJobAction extends AbstractArchitectAction {

    private static final Logger logger = Logger.getLogger(KettleJobAction.class);
    
    public KettleJobAction(ArchitectFrame frame) {
        super(frame, Messages.getString("KettleJobAction.name"), Messages.getString("KettleJobAction.description")); //$NON-NLS-1$ //$NON-NLS-2$
        putValue(SHORT_DESCRIPTION, Messages.getString("KettleJobAction.shortDescription")); //$NON-NLS-1$
    }
    
    public void actionPerformed(ActionEvent arg0) {
        logger.debug("Starting to create a Kettle job."); //$NON-NLS-1$
        
        JDialog d;
        final JPanel cp = new JPanel(new BorderLayout(12,12));
        cp.setBorder(BorderFactory.createEmptyBorder(12,12,12,12));
        final KettleJobPanel kettleETLPanel = new KettleJobPanel(getSession());

        Callable<Boolean> okCall, cancelCall;
        okCall = new Callable<Boolean>() {
            
            public Boolean call() {
                if (!kettleETLPanel.applyChanges()) {
                    return Boolean.FALSE;
                }
                KettleRepositoryDirectoryChooser chooser = new UserRepositoryDirectoryChooser(frame);
                final KettleJob kettleJob = getSession().getKettleJob();
                kettleJob.setRepositoryDirectoryChooser(chooser);
                
                final JDialog createKettleJobMonitor = new JDialog(frame);
                createKettleJobMonitor.setTitle(Messages.getString("KettleJobAction.progressDialogTitle")); //$NON-NLS-1$
                FormLayout layout = new FormLayout("pref", ""); //$NON-NLS-1$ //$NON-NLS-2$
                DefaultFormBuilder builder = new DefaultFormBuilder(layout);
                builder.setDefaultDialogBorder();
                builder.append(Messages.getString("KettleJobAction.progressDialogTitle")); //$NON-NLS-1$
                builder.nextLine();
                JProgressBar progressBar = new JProgressBar();
                builder.append(progressBar);
                builder.nextLine();
                JButton cancel = new JButton(Messages.getString("KettleJobAction.cancelOption")); //$NON-NLS-1$
                cancel.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        kettleJob.setCancelled(true);
                    }
                });
                builder.append(cancel);
                createKettleJobMonitor.add(builder.getPanel());
                
                SPSwingWorker compareWorker = new SPSwingWorker(getSession()) {

                    @Override
                    public void doStuff() throws Exception {
                        createKettleJobMonitor.pack();
                        createKettleJobMonitor.setLocationRelativeTo(frame);
                        createKettleJobMonitor.setVisible(true);
                        List<SQLTable> tableList = getSession().getPlayPen().getTables();
                        kettleJob.doSplitedJobExport(tableList, getSession().getTargetDatabase());
                    }

                    @Override
                    public void cleanup() throws Exception {
                        createKettleJobMonitor.dispose();
                        if (getDoStuffException() != null) {
                            Throwable ex = getDoStuffException();
                            if (ex instanceof SQLObjectException) {
                                ASUtils.showExceptionDialog(getSession(), Messages.getString("KettleJobAction.errorReadingTables"), ex); //$NON-NLS-1$
                            } else if (ex instanceof RuntimeException || ex instanceof IOException || ex instanceof SQLException) {
                                StringBuffer buffer = new StringBuffer();
                                buffer.append(Messages.getString("KettleJobAction.runtimeError")).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
                                for(String task: kettleJob.getTasksToDo()) {
                                    buffer.append(task).append("\n"); //$NON-NLS-1$
                                }
                                ASUtils.showExceptionDialog(getSession(), buffer.toString(), ex);
                            } else if (ex instanceof KettleException) {
                                ASUtils.showExceptionDialog(getSession(), Messages.getString("KettleJobAction.kettleExceptionDuringExport") + //$NON-NLS-1$
                                        "\n" + ex.getMessage().trim(), ex); //$NON-NLS-1$
                            } else {
                                ASUtils.showExceptionDialog(getSession(), Messages.getString("KettleJobAction.unexpectedExceptionDuringExport"), ex); //$NON-NLS-1$
                            }
                            return;
                        }
                        final JDialog toDoListDialog = new JDialog(frame);
                        toDoListDialog.setTitle(Messages.getString("KettleJobAction.kettleTasksDialogTitle")); //$NON-NLS-1$
                        FormLayout layout = new FormLayout("10dlu, 2dlu, fill:pref:grow, 12dlu", "pref, fill:pref:grow, pref"); //$NON-NLS-1$ //$NON-NLS-2$
                        DefaultFormBuilder builder = new DefaultFormBuilder(layout);
                        builder.setDefaultDialogBorder();
                        ButtonBarBuilder buttonBarBuilder = new ButtonBarBuilder();
                        JTextArea toDoList = new JTextArea(10, 60);
                        toDoList.setEditable(false);
                        List<String> tasksToDo = kettleJob.getTasksToDo();
                        for (String task: tasksToDo) {
                            toDoList.append(task + "\n"); //$NON-NLS-1$
                        }
                        JButton close = new JButton(Messages.getString("KettleJobAction.closeOption")); //$NON-NLS-1$
                        close.addActionListener(new ActionListener(){
                            public void actionPerformed(ActionEvent arg0) {
                                toDoListDialog.dispose();
                            }
                        });
                        builder.nextColumn(2);
                        builder.append(Messages.getString("KettleJobAction.kettleTasksInstructions")); //$NON-NLS-1$
                        builder.nextLine();
                        builder.append(""); //$NON-NLS-1$
                        builder.append(new JScrollPane(toDoList));
                        builder.nextLine();
                        builder.append(""); //$NON-NLS-1$
                        buttonBarBuilder.addGlue();
                        buttonBarBuilder.addGridded(close);
                        buttonBarBuilder.addGlue();
                        builder.append(buttonBarBuilder.getPanel());
                        toDoListDialog.add(builder.getPanel());
                        toDoListDialog.pack();
                        toDoListDialog.setLocationRelativeTo(frame);
                        //Show only if there is a task to do
                        if(tasksToDo.size() > 0) { 
                            toDoListDialog.setVisible(true);
                        }
                    }
                };
                
                
                new Thread(compareWorker).start();
                ProgressWatcher.watchProgress(progressBar, kettleJob);
                return Boolean.TRUE;
            }
        };
        
        cancelCall = new Callable<Boolean>() {
            public Boolean call() {
                return Boolean.TRUE;
            }
        };
        
        d = DataEntryPanelBuilder.createDataEntryPanelDialog(
                kettleETLPanel,
                getSession().getArchitectFrame(),
                Messages.getString("KettleJobAction.dialogTitle"), Messages.getString("KettleJobAction.okOption"), //$NON-NLS-1$ //$NON-NLS-2$
                okCall, cancelCall);
        d.pack();
        d.setLocationRelativeTo(getSession().getArchitectFrame());
        d.setVisible(true);
    }
} public class OpenProjectAction extends AbstractArchitectAction {

    private static final Logger logger = Logger.getLogger(OpenProjectAction.class);
    
    public static interface FileLoader {
        public void open(ArchitectSwingSession newSession, File f, ArchitectSwingSession openingSession, boolean separateThread);
        public void open(ArchitectSwingSession newSession, InputStream in, ArchitectSwingSession openingSession, boolean separateThread);
    }

    /**
     * Opens a project file into the given session using a separate worker
     * thread. A dialog box with a progress bar will be displayed during the
     * load process, and any errors that are encountered during the load will be
     * displayed in additional dialogs.
     * <p>
     * Note that this method always returns immediately, so as the caller of
     * this method you have no way of knowing if the load has worked/will work.
     * 
     */
    private static FileLoader fileLoader = new FileLoader() {
        public void open(ArchitectSwingSession newSession, File f, ArchitectSwingSession openingSession, boolean separateThread) {
            LoadFileWorker worker;
            try {
                worker = new LoadFileWorker(f, newSession, openingSession);
                if (separateThread) {
                    new Thread(worker).start();
                } else {
                    worker.run();
                }
            } catch (Exception e1) {
                ASUtils.showExceptionDialogNoReport(Messages.getString("OpenProjectAction.errorLoadingFile"), e1); //$NON-NLS-1$
            }
        }
        
        public void open(ArchitectSwingSession newSession, InputStream in, ArchitectSwingSession openingSession, boolean separateThread) {
            LoadFileWorker worker;
            try {
                worker = new LoadFileWorker(in, newSession, openingSession);
                if (separateThread) {
                    new Thread(worker).start();
                } else {
                    worker.run();
                }
            } catch (Exception e1) {
                ASUtils.showExceptionDialogNoReport(Messages.getString("OpenProjectAction.errorLoadingFile"), e1); //$NON-NLS-1$
            }
        }
    };
    
    public static void setFileLoader(FileLoader loader) {
        fileLoader = loader;
    }
    
    public static FileLoader getFileLoader() {
        return fileLoader;
    }

    public OpenProjectAction(ArchitectFrame frame) {
        super(frame, Messages.getString("OpenProjectAction.name"), Messages.getString("OpenProjectAction.description"), "folder"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_O, Toolkit.getDefaultToolkit()
                .getMenuShortcutKeyMask()));
    }

    public void actionPerformed(ActionEvent e) {
        File f;
        if (e.getActionCommand() == null || !e.getActionCommand().startsWith("file:")) {
            JFileChooser chooser = new JFileChooser(getSession().getRecentMenu().getMostRecentFile());
            chooser.addChoosableFileFilter(SPSUtils.ARCHITECT_FILE_FILTER);
            int returnVal = chooser.showOpenDialog(frame);
            if (returnVal == JFileChooser.APPROVE_OPTION) {
                f = chooser.getSelectedFile();
            } else {
                return;
            }
        } else {
            f = new File(e.getActionCommand().substring("file:".length()));
        }
        try {
            fileLoader.open(getSession().getContext().createSession(), f, getSession(), true);
        } catch (SQLObjectException ex) {
            SPSUtils.showExceptionDialogNoReport(getSession().getArchitectFrame(),
                    Messages.getString("OpenProjectAction.failedToOpenProjectFile"), ex); //$NON-NLS-1$
        }
    }
    
    /**
     * A worker for asynchronously loading a new project file.
     */
    public static class LoadFileWorker extends SPSwingWorker {
        private final ArchitectSwingSessionContext context;

        private final InputStream in;

        private final File file;

        private final RecentMenu recent;

        private final ArchitectSwingSession openingSession;

        /**
         * The session that will get created if loading the file in doStuff() is
         * successful.
         */
        private ArchitectSwingSession session;

        /**
         * Load file worker creates a new worker and opens the given file.
         * 
         * @param file
         *            this file gets opened in the constructor
         * @param newSession
         *            The session in which the project file should be opened
         * @param openingSession
         *            The session from which the open project operation is being
         *            called. Should not be null and should have a frame. If the
         *            openingSession.isNew() returns true, (i.e. it's an new,
         *            empty, and unmodified project) then openingSession.close()
         *            will be called once the project is finished loading.
         * @throws SQLObjectException
         *             when the project creation fails.
         * @throws FileNotFoundException
         *             if file doesn't exist
         */
        public LoadFileWorker(File file, ArchitectSwingSession newSession, ArchitectSwingSession openingSession)
                throws SQLObjectException, FileNotFoundException {
            // The super constructor registers the LoadFileWorker with the
            // session.
            super(newSession);
            this.context = newSession.getContext();
            this.file = file;
            this.recent = newSession.getRecentMenu();
            this.openingSession = openingSession;

            this.session = newSession;

            // This assumes that the opening session has a frame. 
            // To get rid of this requirement, we need to create an
            // invisible owner frame in the context. we can set its icon
            // to the architect icon, and use it as the owner of all
            // "unowned" dialogs like this one.
            in = new BufferedInputStream(new ProgressMonitorInputStream(openingSession.getArchitectFrame(),
                    Messages.getString("OpenProjectAction.reading") + file.getName(), new FileInputStream(file))); //$NON-NLS-1$
        }
        
        public LoadFileWorker(InputStream in, ArchitectSwingSession newSession, ArchitectSwingSession openingSession) {
            super(newSession);
            this.context = newSession.getContext();
            file = null;
            this.recent = newSession.getRecentMenu();
            this.openingSession = openingSession;

            this.session = newSession;
            
            this.in = in;
        }

        @Override
        public void doStuff() throws Exception {
            session.getProjectLoader().load(in, session.getDataSources(), openingSession);
            session.getProjectLoader().setFile(file);
        }

        @Override
        public void cleanup() throws SQLObjectException {
            if (getDoStuffException() != null) {
                Throwable cause = getDoStuffException().getCause();
                // This if clause is to prevent an error from being thrown if
                // the user cancelled the file load,
                // in which ProgressMonitorInputStream throws an
                // InterruptedIOException with message "progress",
                // or if the user cancelled because of a warning when
                // they tried to open an incompatible file
                if (!(getDoStuffException() instanceof InterruptedIOException) &&
                        (!(cause instanceof InterruptedIOException) || !(cause.getMessage().equals("progress")))) { //$NON-NLS-1$
                    // We have to use the non-session exception dialogue here,
                    // because there is no session available (we just failed to
                    // create one!)
                    ASUtils.showExceptionDialogNoReport(Messages.getString("OpenProjectAction.cannotOpenProjectFile") + file.getAbsolutePath(), //$NON-NLS-1$
                            getDoStuffException());
                    logger.error("Got exception while opening a project", getDoStuffException()); //$NON-NLS-1$
                }
                session.removeSwingWorker(this);
                if (session.getContext().getSessions().size() > 1) {
                    //Prevents the save dialog from being displayed if it contained an error
                    session.getProjectLoader().setModified(false); 
                    session.close();
                }
            } else {
                if (file != null) {
                    recent.putRecentFileName(file.getAbsolutePath());
                }
                if (openingSession != null) {
                    openingSession.getArchitectFrame().addSession(session);
                    openingSession.getArchitectFrame().setCurrentSession(session);
                }
                ((DBTreeModel) session.getDBTree().getModel()).refreshTreeStructure();
            }

            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException ie) {
                logger.error("got exception while closing project file", ie); //$NON-NLS-1$
            }
        }
    }
}
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.awt.event.WindowStateListener;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.prefs.Preferences;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import javax.swing.tree.TreePath;
import org.apache.log4j.Logger;
import ca.sqlpower.architect.ArchitectSession;
import ca.sqlpower.architect.CoreUserSettings;
import ca.sqlpower.architect.UpdateCheckSettings;
import ca.sqlpower.architect.UserSettings;
import ca.sqlpower.architect.enterprise.ArchitectClientSideSession;
import ca.sqlpower.architect.layout.ArchitectLayout;
import ca.sqlpower.architect.layout.FruchtermanReingoldForceLayout;
import ca.sqlpower.architect.olap.OLAPSession;
import ca.sqlpower.architect.swingui.PlayPen.CancelableListener;
import ca.sqlpower.architect.swingui.action.AboutAction;
import ca.sqlpower.architect.swingui.action.AlignTableAction;
import ca.sqlpower.architect.swingui.action.AutoLayoutAction;
import ca.sqlpower.architect.swingui.action.CheckForUpdateAction;
import ca.sqlpower.architect.swingui.action.CloseProjectAction;
import ca.sqlpower.architect.swingui.action.CompareDMAction;
import ca.sqlpower.architect.swingui.action.CopySelectedAction;
import ca.sqlpower.architect.swingui.action.CreateRelationshipAction;
import ca.sqlpower.architect.swingui.action.CreateTableAction;
import ca.sqlpower.architect.swingui.action.CutSelectedAction;
import ca.sqlpower.architect.swingui.action.DataMoverAction;
import ca.sqlpower.architect.swingui.action.DataSourcePropertiesAction;
import ca.sqlpower.architect.swingui.action.DatabaseConnectionManagerAction;
import ca.sqlpower.architect.swingui.action.DeleteSelectedAction;
import ca.sqlpower.architect.swingui.action.EditColumnAction;
import ca.sqlpower.architect.swingui.action.EditCriticSettingsAction;
import ca.sqlpower.architect.swingui.action.EditLabelAction;
import ca.sqlpower.architect.swingui.action.EditRelationshipAction;
import ca.sqlpower.architect.swingui.action.EditSelectedAction;
import ca.sqlpower.architect.swingui.action.EditSelectedIndexAction;
import ca.sqlpower.architect.swingui.action.EditSpecificIndexAction;
import ca.sqlpower.architect.swingui.action.EditTableAction;
import ca.sqlpower.architect.swingui.action.ExportCSVAction;
import ca.sqlpower.architect.swingui.action.ExportDDLAction;
import ca.sqlpower.architect.swingui.action.ExportHTMLReportAction;
import ca.sqlpower.architect.swingui.action.ExportPlaypenToPDFAction;
import ca.sqlpower.architect.swingui.action.FocusToChildOrParentTableAction;
import ca.sqlpower.architect.swingui.action.InsertColumnAction;
import ca.sqlpower.architect.swingui.action.InsertIndexAction;
import ca.sqlpower.architect.swingui.action.InvadersAction;
import ca.sqlpower.architect.swingui.action.KettleJobAction;
import ca.sqlpower.architect.swingui.action.OpenProjectAction;
import ca.sqlpower.architect.swingui.action.PasteSelectedAction;
import ca.sqlpower.architect.swingui.action.PreferencesAction;
import ca.sqlpower.architect.swingui.action.PrintAction;
import ca.sqlpower.architect.swingui.action.ProfileAction;
import ca.sqlpower.architect.swingui.action.ProjectSettingsAction;
import ca.sqlpower.architect.swingui.action.RedoAction;
import ca.sqlpower.architect.swingui.action.RemoveSourceDBAction;
import ca.sqlpower.architect.swingui.action.ReverseRelationshipAction;
import ca.sqlpower.architect.swingui.action.SQLQueryAction;
import ca.sqlpower.architect.swingui.action.SearchReplaceAction;
import ca.sqlpower.architect.swingui.action.SelectAllAction;
import ca.sqlpower.architect.swingui.action.UndoAction;
import ca.sqlpower.architect.swingui.action.VisualMappingReportAction;
import ca.sqlpower.architect.swingui.action.ZoomAction;
import ca.sqlpower.architect.swingui.action.ZoomResetAction;
import ca.sqlpower.architect.swingui.action.ZoomToFitAction;
import ca.sqlpower.architect.swingui.action.enterprise.RefreshProjectAction;
import ca.sqlpower.architect.swingui.enterprise.ArchitectServerProjectsManagerPanel;
import ca.sqlpower.architect.swingui.enterprise.ProjectSecurityPanel;
import ca.sqlpower.architect.swingui.enterprise.RevisionListPanel;
import ca.sqlpower.architect.swingui.enterprise.SecurityPanel;
import ca.sqlpower.architect.swingui.event.SelectionEvent;
import ca.sqlpower.architect.swingui.event.SelectionListener;
import ca.sqlpower.architect.swingui.olap.action.ImportSchemaAction;
import ca.sqlpower.architect.swingui.olap.action.OLAPEditAction;
import ca.sqlpower.architect.swingui.olap.action.OLAPSchemaManagerAction;
import ca.sqlpower.enterprise.client.ConnectionTestAction;
import ca.sqlpower.enterprise.client.ProjectLocation;
import ca.sqlpower.enterprise.client.SPServerInfo;
import ca.sqlpower.sqlobject.SQLColumn;
import ca.sqlpower.sqlobject.SQLDatabase;
import ca.sqlpower.sqlobject.SQLObjectException;
import ca.sqlpower.sqlobject.SQLTable.TransferStyles;
import ca.sqlpower.swingui.RecentMenu;
import ca.sqlpower.swingui.SPSUtils;
import ca.sqlpower.swingui.StackedTabComponent;
import ca.sqlpower.swingui.SwingUIUserPrompterFactory;
import ca.sqlpower.swingui.StackedTabComponent.StackedTab;
import ca.sqlpower.swingui.SwingUIUserPrompterFactory.NonModalSwingUIUserPrompterFactory;
import ca.sqlpower.swingui.action.OpenUrlAction;
import ca.sqlpower.swingui.enterprise.client.SPServerInfoManagerPanel;
import ca.sqlpower.swingui.enterprise.client.ServerProjectsManagerPanel;
import ca.sqlpower.swingui.event.SessionLifecycleEvent;
import ca.sqlpower.swingui.event.SessionLifecycleListener;
import ca.sqlpower.util.BrowserUtil;
import ca.sqlpower.util.UserPrompterFactory;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
/*
 * Copyright (c) 2008, SQL Power Group Inc.
 *
 * This file is part of Power*Architect.
 *
 * Power*Architect is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Power*Architect is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 */
package ca.sqlpower.architect.swingui;






/**
 * The Main Window for the Architect Application; contains a main() method that is
 * the conventional way to start the application running.
 */
public class ArchitectFrame extends JFrame {

    private static final String CYCLE_TAB_ACTION = "ca.sqlpower.architect.swingui.CYCLE_TAB_ACTION";

    private static Logger logger = Logger.getLogger(ArchitectFrame.class);
    
    private static final ImageIcon GROUP_ICON = new ImageIcon(ArchitectFrame.class.getResource("enterprise/icons/group.png"));

    public static final double ZOOM_STEP = 0.25;
    
    private ArchitectSwingSessionContext context;
    private ArchitectSwingSession currentSession = null;
    private List<ArchitectSwingSession> sessions = new ArrayList<ArchitectSwingSession>();
    private JToolBar projectBar = null;
    private JToolBar ppBar = null;
    private JMenuBar menuBar = null;
    private JSplitPane splitPane = null;
    private StackedTabComponent stackedTabPane = new StackedTabComponent();
    private BiMap<ArchitectSwingSession, StackedTab> sessionTabs = HashBiMap.create();
    
    private Navigator navigatorDialog;
    
    private int oldWidth;
    private int oldHeight;
    private int prefWidth;
    private int prefHeight;
    
    private final UserPrompterFactory nonModalUserPrompterFactory;
    
    private final DropTargetListener tabDropTargetListener = new TabDropTargetListener();

    private JMenu connectionsMenu;
    private ArchitectLayout autoLayout;
    private UndoAction undoAction;
    private RedoAction redoAction;
    
    private AboutAction aboutAction;
    private Action newProjectAction;
    private Action newWindowAction;
    private OpenProjectAction openProjectAction;