Revision: 3370
Author: [email protected]
Date: Wed Mar 10 08:21:50 2010
Log: Updated the Architect and SQLObjects to push all events onto the foreground thread. Previously the current thread was
always considered the foreground thread.
Changes to push events to the foreground thread:
- Populate on the SQLObjects now has all of the objects created on the foreground thread. - All of the columns in all tables get populated at the same time. This improves the performance of populating columns. - As a side effect the indices of tables will not always be populated immediately after columns are populated so
    there are more places where populateIndices are called in SQLTable.
- The correct session is used by the ArchitectProject to get the foreground thread.

Additional changes made to correct this problem:
- The getSession on the ArchitectProject throws a correct exception when the session is null. - The childrenInaccessibleReason has been updated to allow an exception for each child type of the objects. - The fix to preventing an infinite loop between populate and the DBTreeModel has been updated. Now the tree model will collect events and update the tree at the end of a transaction.

http://code.google.com/p/power-architect/source/detail?r=3370

Modified:
 /trunk
 /trunk/regress/ca/sqlpower/architect/ArchitectProjectTest.java
 /trunk/regress/ca/sqlpower/architect/profile/ProfileManagerImplTest.java
 /trunk/regress/ca/sqlpower/architect/profile/TestProfileBase.java
 /trunk/regress/ca/sqlpower/architect/swingui/TestSwingUIProject.java
/trunk/regress/ca/sqlpower/architect/swingui/TestingArchitectSwingSession.java
 /trunk/regress/ca/sqlpower/architect/util/ArchitectNewValueMaker.java
 /trunk/src/ca/sqlpower/architect/ArchitectProject.java
 /trunk/src/ca/sqlpower/architect/ArchitectSessionImpl.java
 /trunk/src/ca/sqlpower/architect/ProjectLoader.java
 /trunk/src/ca/sqlpower/architect/messages.properties
 /trunk/src/ca/sqlpower/architect/profile/ProfileManagerImpl.java
 /trunk/src/ca/sqlpower/architect/swingui/ArchitectSwingSession.java
 /trunk/src/ca/sqlpower/architect/swingui/ArchitectSwingSessionImpl.java
 /trunk/src/ca/sqlpower/architect/swingui/DBTree.java
 /trunk/src/ca/sqlpower/architect/swingui/PlayPen.java
 /trunk/src/ca/sqlpower/architect/swingui/SwingUIProjectLoader.java
 /trunk/src/ca/sqlpower/architect/swingui/dbtree/DBTreeCellRenderer.java
 /trunk/src/ca/sqlpower/architect/swingui/dbtree/DBTreeModel.java

=======================================
--- /trunk/regress/ca/sqlpower/architect/ArchitectProjectTest.java Tue Feb 16 14:02:41 2010 +++ /trunk/regress/ca/sqlpower/architect/ArchitectProjectTest.java Wed Mar 10 08:21:50 2010
@@ -38,7 +38,7 @@
         super.setUp();
         ArchitectSession session = new StubArchitectSession();
         objectUnderTest = new ArchitectProject();
-        objectUnderTest.init(session);
+        objectUnderTest.setSession(session);
         getRootObject().addChild(objectUnderTest, 0);
     }

=======================================
--- /trunk/regress/ca/sqlpower/architect/profile/ProfileManagerImplTest.java Wed Feb 17 15:05:17 2010 +++ /trunk/regress/ca/sqlpower/architect/profile/ProfileManagerImplTest.java Wed Mar 10 08:21:50 2010
@@ -41,9 +41,14 @@
         super.setUp();

         profileManager = new ProfileManagerImpl();
- ArchitectProject project = (ArchitectProject) new ArchitectNewValueMaker( + final ArchitectProject project = (ArchitectProject) new ArchitectNewValueMaker( getRootObject(), getPLIni()).makeNewValue(ArchitectProject.class, null, "");
-        project.init(new StubArchitectSession());
+        project.setSession(new StubArchitectSession() {
+            @Override
+            public ArchitectProject getWorkspace() {
+                return project;
+            }
+        });
         project.setProfileManager(profileManager);

         getRootObject().addChild(project, 0);
=======================================
--- /trunk/regress/ca/sqlpower/architect/profile/TestProfileBase.java Fri Feb 12 07:43:48 2010 +++ /trunk/regress/ca/sqlpower/architect/profile/TestProfileBase.java Wed Mar 10 08:21:50 2010
@@ -176,7 +176,6 @@
             assertNotNull(t3);

             pm = new ProfileManagerImpl();
-            ((ProfileManagerImpl) pm).setUserPrompterFactory(session);
             session.getWorkspace().setProfileManager(pm);
             pm.getDefaultProfileSettings().setFindingAvg(true);
             pm.getDefaultProfileSettings().setFindingMin(true);
=======================================
--- /trunk/regress/ca/sqlpower/architect/swingui/TestSwingUIProject.java Thu Mar 4 15:28:57 2010 +++ /trunk/regress/ca/sqlpower/architect/swingui/TestSwingUIProject.java Wed Mar 10 08:21:50 2010
@@ -483,7 +483,7 @@
// grab the second database in the dbtree's model (the first is the play pen) db = (SQLDatabase) session2.getSourceDatabases().getDatabaseList().get(1);

- System.out.println("DB has child exception " + db.getChildrenInaccessibleReason()); + System.out.println("DB has child exception " + db.getChildrenInaccessibleReasons());

                Map<String, Object> newDescription =
ca.sqlpower.testutil.TestUtils.getAllInterestingProperties(db, propertiesToIgnore);
=======================================
--- /trunk/regress/ca/sqlpower/architect/swingui/TestingArchitectSwingSession.java Thu Feb 25 09:46:15 2010 +++ /trunk/regress/ca/sqlpower/architect/swingui/TestingArchitectSwingSession.java Wed Mar 10 08:21:50 2010
@@ -41,7 +41,6 @@
 import ca.sqlpower.architect.olap.OLAPRootObject;
 import ca.sqlpower.architect.olap.OLAPSession;
 import ca.sqlpower.architect.profile.ProfileManager;
-import ca.sqlpower.architect.profile.ProfileManagerImpl;
import ca.sqlpower.architect.swingui.ArchitectSwingSessionImpl.ColumnVisibility;
 import ca.sqlpower.architect.swingui.olap.OLAPEditSession;
 import ca.sqlpower.architect.undo.ArchitectUndoManager;
@@ -98,7 +97,7 @@
             }
         };
         this.delegateSession = new ArchitectSessionImpl(context, "test");
- ((ProfileManagerImpl) delegateSession.getProfileManager()).setUserPrompterFactory(this);
+        delegateSession.getWorkspace().setSession(this);
         project = new SwingUIProjectLoader(this);
         userSettings = context.getUserSettings();
         sourceDatabases = new DBTree(this);
@@ -384,18 +383,19 @@
     }

     public boolean isForegroundThread() {
-        // TODO Auto-generated method stub
-        return false;
+        return true;
     }

     public void runInBackground(Runnable runner) {
-        // TODO Auto-generated method stub
-
+        runner.run();
+    }
+
+    public void runInBackground(Runnable runner, String threadName) {
+        runner.run();
     }

     public void runInForeground(Runnable runner) {
-        // TODO Auto-generated method stub
-
+        runner.run();
     }

     public void addPropertyChangeListener(PropertyChangeListener l) {
=======================================
--- /trunk/regress/ca/sqlpower/architect/util/ArchitectNewValueMaker.java Thu Feb 18 07:51:21 2010 +++ /trunk/regress/ca/sqlpower/architect/util/ArchitectNewValueMaker.java Wed Mar 10 08:21:50 2010
@@ -31,7 +31,6 @@
 import ca.sqlpower.sqlobject.SQLObjectException;
 import ca.sqlpower.sqlobject.SQLTable;
 import ca.sqlpower.testutil.GenericNewValueMaker;
-import ca.sqlpower.util.SPSession;

 public class ArchitectNewValueMaker extends GenericNewValueMaker {

@@ -69,12 +68,7 @@
             ArchitectProject project;
             final SPObject rootObject = getRootObject();
             try {
-                project = new ArchitectProject() {
-                    @Override
-                    public SPSession getSession() {
-                        return rootObject.getSession();
-                    }
-                };
+                project = new ArchitectProject();
             } catch (SQLObjectException e) {
                 throw new RuntimeException(e);
             }
=======================================
--- /trunk/src/ca/sqlpower/architect/ArchitectProject.java Tue Feb 16 14:02:41 2010 +++ /trunk/src/ca/sqlpower/architect/ArchitectProject.java Wed Mar 10 08:21:50 2010
@@ -31,6 +31,7 @@
 import ca.sqlpower.object.annotation.Accessor;
 import ca.sqlpower.object.annotation.Constructor;
 import ca.sqlpower.object.annotation.ConstructorParameter;
+import ca.sqlpower.object.annotation.Mutator;
 import ca.sqlpower.object.annotation.NonBound;
 import ca.sqlpower.object.annotation.NonProperty;
 import ca.sqlpower.object.annotation.Transient;
@@ -40,7 +41,7 @@
 import ca.sqlpower.sqlobject.SQLObject;
 import ca.sqlpower.sqlobject.SQLObjectException;
 import ca.sqlpower.sqlobject.SQLObjectRoot;
-import ca.sqlpower.util.SPSession;
+import ca.sqlpower.util.SessionNotFoundException;

 /**
  *
@@ -68,6 +69,11 @@
     private final SQLObjectRoot rootObject;
     private ProfileManager profileManager;

+    /**
+     * The current integrity watcher on the project.
+     */
+    private SourceObjectIntegrityWatcher currentWatcher;
+
     /**
* Constructs an architect project. The init method must be called immediately
      * after creating a project.
@@ -93,16 +99,24 @@
public ArchitectProject(@ConstructorParameter(isProperty=ParameterType.CHILD, propertyName="rootObject") SQLObjectRoot rootObject)
             throws SQLObjectException {
         this.rootObject = rootObject;
+        rootObject.setParent(this);
+        setName("Architect Project");
     }

     /**
      * Call this to initialize the session and the children of the project.
      */
-    public void init(ArchitectSession session) {
+    @Transient @Mutator
+    public void setSession(ArchitectSession session) {
+        if (this.session != null) {
+            rootObject.removeSQLObjectPreEventListener(currentWatcher);
+            currentWatcher = null;
+        }
         this.session = session;
- rootObject.addSQLObjectPreEventListener(new SourceObjectIntegrityWatcher(session));
-        rootObject.setParent(this);
-        setName("Architect Project");
+        if (this.session != null) {
+            currentWatcher = new SourceObjectIntegrityWatcher(session);
+            rootObject.addSQLObjectPreEventListener(currentWatcher);
+        }
     }

     /**
@@ -187,8 +201,13 @@
     }

     @Transient @Accessor
-    public SPSession getSession() {
-        return session;
+    public ArchitectSession getSession() throws SessionNotFoundException {
+        if (session != null) {
+            return session;
+        } else {
+ throw new SessionNotFoundException("The project has not been given a session yet. " + + "This should only happen during the construction of the project.");
+        }
     }

     public boolean allowsChildren() {
=======================================
--- /trunk/src/ca/sqlpower/architect/ArchitectSessionImpl.java Thu Feb 25 09:46:15 2010 +++ /trunk/src/ca/sqlpower/architect/ArchitectSessionImpl.java Wed Mar 10 08:21:50 2010
@@ -87,9 +87,8 @@

            this.context = context;
            this.project = new ArchitectProject();
-           project.init(this);
+           project.setSession(this);
            ProfileManagerImpl manager = new ProfileManagerImpl();
-           manager.setUserPrompterFactory(this);
            this.project.setProfileManager(manager);
            this.name = name;
         this.projectLoader = new ProjectLoader(this);
@@ -210,7 +209,7 @@
     public void runInForeground(Runnable runner) {
         runner.run();
     }
-
+
     public void addPropertyChangeListener(PropertyChangeListener l) {
         pcs.addPropertyChangeListener(l);
     }
=======================================
--- /trunk/src/ca/sqlpower/architect/ProjectLoader.java Thu Mar 4 09:45:30 2010 +++ /trunk/src/ca/sqlpower/architect/ProjectLoader.java Wed Mar 10 08:21:50 2010
@@ -65,6 +65,7 @@
 import ca.sqlpower.sqlobject.SQLIndex.AscendDescend;
 import ca.sqlpower.sqlobject.SQLIndex.Column;
 import ca.sqlpower.sqlobject.SQLRelationship.Deferrability;
+import ca.sqlpower.sqlobject.SQLRelationship.SQLImportedKey;
 import ca.sqlpower.sqlobject.SQLRelationship.UpdateDeleteRule;
 import ca.sqlpower.swingui.SPSUtils;
 import ca.sqlpower.util.BrowserUtil;
@@ -95,7 +96,7 @@
         String message = attr.getValue("sql-exception");
         if (message != null) {
             try {
- obj.setChildrenInaccessibleReason(new SQLObjectException(message), false); + obj.setChildrenInaccessibleReason(new SQLObjectException(message), SQLObject.class, false);
             } catch (SQLObjectException e) {
                 throw new AssertionError("Unreachable code");
             }
@@ -628,10 +629,10 @@
             return tab;
         }
     }
-
+
     /**
- * XXX Temporary factory for folders until the file format changes and the folders
-     * are removed permanently.
+ * XXX Temporary factory for folders until the file format changes and the
+     * folders are removed permanently.
      */
     private class SQLFolderFactory extends AbstractObjectCreationFactory {
         @Override
@@ -639,14 +640,48 @@
String type = attributes.getValue("type"); //1=col, 2=import, 3=export, 4=index boolean isPopulated = Boolean.valueOf(attributes.getValue("populated"));

+            String message = attributes.getValue("sql-exception");
+
             if (type.equals("1")) {
                 currentTable.setColumnsPopulated(isPopulated);
+                if (message != null) {
+                    try {
+ currentTable.setChildrenInaccessibleReason(new SQLObjectException(message),
+                                SQLColumn.class, false);
+                    } catch (SQLObjectException e) {
+                        throw new AssertionError("Unreachable code");
+                    }
+                }
             } else if (type.equals("2")) {
                 currentTable.setImportedKeysPopulated(isPopulated);
+                if (message != null) {
+                    try {
+ currentTable.setChildrenInaccessibleReason(new SQLObjectException(message),
+                                SQLImportedKey.class, false);
+                    } catch (SQLObjectException e) {
+                        throw new AssertionError("Unreachable code");
+                    }
+                }
             } else if (type.equals("3")) {
                 currentTable.setExportedKeysPopulated(isPopulated);
+                if (message != null) {
+                    try {
+ currentTable.setChildrenInaccessibleReason(new SQLObjectException(message),
+                                SQLRelationship.class, false);
+                    } catch (SQLObjectException e) {
+                        throw new AssertionError("Unreachable code");
+                    }
+                }
             } else if (type.equals("4")) {
                 currentTable.setIndicesPopulated(isPopulated);
+                if (message != null) {
+                    try {
+ currentTable.setChildrenInaccessibleReason(new SQLObjectException(message),
+                                SQLIndex.class, false);
+                    } catch (SQLObjectException e) {
+                        throw new AssertionError("Unreachable code");
+                    }
+                }
             }

             return currentTable;
=======================================
--- /trunk/src/ca/sqlpower/architect/messages.properties Thu Aug 7 09:26:17 2008 +++ /trunk/src/ca/sqlpower/architect/messages.properties Wed Mar 10 08:21:50 2010
@@ -1,4 +1,8 @@
 cancel=Cancel
+columnsUnpopulatedOnLoad=The columns of this table were not populated in the loaded file. To populate these columns refresh the data source. +importedKeysUnpopulatedOnLoad=The imported keys of this table were not populated in the loaded file. To populate these columns refresh the data source. +exportedKeysUnpopulatedOnLoad=The exported keys of this table were not populated in the loaded file. To populate these columns refresh the data source. +indicesUnpopulatedOnLoad=The indices of this table were not populated in the loaded file. To populate these columns refresh the data source.
 ok=OK
 SourceObjectIntegrityWatcher.forgetLineageOption=Forget Lineage
SourceObjectIntegrityWatcher.keepSourceConnectionOption=Keep Source Connection
=======================================
--- /trunk/src/ca/sqlpower/architect/profile/ProfileManagerImpl.java Thu Feb 18 13:18:27 2010 +++ /trunk/src/ca/sqlpower/architect/profile/ProfileManagerImpl.java Wed Mar 10 08:21:50 2010
@@ -32,7 +32,6 @@
 import org.apache.log4j.Logger;

 import ca.sqlpower.architect.ArchitectProject;
-import ca.sqlpower.architect.ArchitectSession;
 import ca.sqlpower.architect.profile.event.ProfileChangeEvent;
 import ca.sqlpower.architect.profile.event.ProfileChangeListener;
 import ca.sqlpower.object.AbstractSPObject;
@@ -50,7 +49,6 @@
 import ca.sqlpower.sqlobject.SQLObjectPreEventListener;
 import ca.sqlpower.sqlobject.SQLTable;
 import ca.sqlpower.util.UserPrompter;
-import ca.sqlpower.util.UserPrompterFactory;
 import ca.sqlpower.util.UserPrompter.UserPromptOptions;
 import ca.sqlpower.util.UserPrompter.UserPromptResponse;
 import ca.sqlpower.util.UserPrompterFactory.UserPromptType;
@@ -82,7 +80,7 @@

         public void dbChildrenPreRemove(SQLObjectPreEvent e) {
             logger.debug("Pre-remove on profile manager");
-            UserPrompter up = session.createUserPrompter(
+            UserPrompter up = getParent().getSession().createUserPrompter(
"{0} tables have been profiled from the database {1}.\n" +
                     "\n" +
"If you proceed, the profiling information from the database" +
@@ -130,11 +128,6 @@
      */
private final List<TableProfileResult> results = new ArrayList<TableProfileResult>();

-    /**
-     * The user prompter for the profile manager.
-     */
-    private UserPrompterFactory session;
-
     /**
      * The defaults that new profile results will be created with.
      * XXX these are specific to the remote database profiler!
@@ -202,11 +195,6 @@
         defaultProfileSettings.setParent(this);
         setName("Profile Manager");
     }
-
-    @Transient @Mutator
-    public void setUserPrompterFactory(ArchitectSession session) {
-        this.session = session;
-    }

     @Override @Mutator
     public void setParent(SPObject parent) {
@@ -215,8 +203,9 @@
((ArchitectProject) getParent()).getRootObject().removeSQLObjectPreEventListener(databaseRemovalWatcher);
         }
         super.setParent(parent);
- if (parent != null && ((ArchitectProject) parent).getRootObject() != null) { - ((ArchitectProject) parent).getRootObject().addSQLObjectPreEventListener(databaseRemovalWatcher); + final ArchitectProject architectProject = (ArchitectProject) parent;
+        if (parent != null && architectProject.getRootObject() != null) {
+ architectProject.getRootObject().addSQLObjectPreEventListener(databaseRemovalWatcher);
         }
         firePropertyChange("parent", oldParent, parent);
     }
=======================================
--- /trunk/src/ca/sqlpower/architect/swingui/ArchitectSwingSession.java Thu Feb 25 09:46:15 2010 +++ /trunk/src/ca/sqlpower/architect/swingui/ArchitectSwingSession.java Wed Mar 10 08:21:50 2010
@@ -329,4 +329,12 @@
     public boolean isEnterpriseSession();

     public ArchitectClientSideSession getEnterpriseSession();
-}
+
+    /**
+     * Works the same as {...@link #runInBackground(Runnable)} but lets you
+     * name the background thread for easier debugging.
+     * @param runner
+     * @param threadName
+     */
+    public void runInBackground(Runnable runner, String threadName);
+}
=======================================
--- /trunk/src/ca/sqlpower/architect/swingui/ArchitectSwingSessionImpl.java Thu Mar 4 09:45:30 2010 +++ /trunk/src/ca/sqlpower/architect/swingui/ArchitectSwingSessionImpl.java Wed Mar 10 08:21:50 2010
@@ -261,9 +261,9 @@
         this.isNew = true;
         this.context = context;
         this.delegateSession = delegateSession;
+        delegateSession.getWorkspace().setSession(this);
         this.olapRootObject = new OLAPRootObject(delegateSession);
         ProfileManagerImpl profileManager = new ProfileManagerImpl();
-        profileManager.setUserPrompterFactory(this);
((ArchitectSessionImpl)delegateSession).setProfileManager(profileManager); ((ArchitectSessionImpl)delegateSession).setUserPrompterFactory(this);
         this.recent = new RecentMenu(this.getClass()) {
@@ -1086,34 +1086,33 @@
     }

     public boolean isForegroundThread() {
-        return SwingUtilities.isEventDispatchThread();
+ //Until the GUI is initialized we may be running headless in which case
+        //we will not be using the EDT.
+        if (frame != null) {
+            return SwingUtilities.isEventDispatchThread();
+        } else {
+            return true;
+        }
     }

-    public void runInBackground(final Runnable runner) {
-        SPSwingWorker worker = new SPSwingWorker(this) {
-
-            @Override
-            public void doStuff() throws Exception {
-                runner.run();
-            }
-
-            @Override
-            public void cleanup() throws Exception {
-                //do nothing
-            }
-
-        };
-        new Thread(worker).start();
+    public void runInBackground(Runnable runner) {
+        runInBackground(runner, "worker");
+    }
+
+    public void runInBackground(final Runnable runner, String name) {
+        new Thread(runner, name).start();
     }

     public void runInForeground(Runnable runner) {
-        if (SwingUtilities.isEventDispatchThread()) {
+ //Until the GUI is initialized we may be running headless in which case
+        //we will not be using the EDT.
+        if (frame == null || SwingUtilities.isEventDispatchThread()) {
             runner.run();
         } else {
             SwingUtilities.invokeLater(runner);
         }
     }
-
+
     public void addPropertyChangeListener(PropertyChangeListener l) {
         delegateSession.addPropertyChangeListener(l);
     }
=======================================
--- /trunk/src/ca/sqlpower/architect/swingui/DBTree.java Thu Mar 4 14:42:47 2010 +++ /trunk/src/ca/sqlpower/architect/swingui/DBTree.java Wed Mar 10 08:21:50 2010
@@ -160,9 +160,12 @@
             public void mouseReleased(MouseEvent e) {
                 if (getPathForLocation(e.getX(), e.getY()) != null) {
Object node = getPathForLocation(e.getX(), e.getY()).getLastPathComponent(); - if (e.getClickCount() == 2 && node instanceof SQLObject && ((SQLObject) node).getChildrenInaccessibleReason() != null) { + if (e.getClickCount() == 2 && node instanceof SQLObject && + !((SQLObject) node).getChildrenInaccessibleReasons().isEmpty()) {
+                        Throwable firstException = ((SQLObject) node).
+ getChildrenInaccessibleReasons().entrySet().iterator().next().getValue(); SPSUtils.showExceptionDialogNoReport(session.getArchitectFrame(), - Messages.getString("DBTree.exceptionNodeReport"), ((SQLObject) node).getChildrenInaccessibleReason()); //$NON-NLS-1$ + Messages.getString("DBTree.exceptionNodeReport"), firstException); //$NON-NLS-1$
                     }
                 }
             }
@@ -286,7 +289,7 @@
                        session.getArchitectFrame().setCursor(null);
                }
        }
-
+
        // ---------- methods of DragSourceListener -----------
        public void dragEnter(DragSourceDragEvent dsde) {
                logger.debug("DBTree: got dragEnter event"); //$NON-NLS-1$
@@ -555,7 +558,7 @@
                                //tree only has one child
if (!tempDB.isCatalogContainer() && !tempDB.isSchemaContainer() &&
                                        (!(tempDB.getChildCount() == 1) ||
-                                               
tempDB.getChildrenInaccessibleReason() == null))
+ tempDB.getChildrenInaccessibleReasons().isEmpty()))
                                {
                                    //a new action is needed to maintain the 
database variable
CompareToCurrentAction compareToCurrentAction = new CompareToCurrentAction();
@@ -618,18 +621,21 @@
                }

// Show exception details (SQLException node can appear anywhere in the hierarchy) - if (p != null && p.getLastPathComponent() instanceof SQLObject && ((SQLObject) p.getLastPathComponent()).getChildrenInaccessibleReason() != null) {
+               if (p != null && p.getLastPathComponent() instanceof SQLObject 
&&
+ !((SQLObject) p.getLastPathComponent()).getChildrenInaccessibleReasons().isEmpty()) {
                        newMenu.addSeparator();
             final SQLObject node = (SQLObject) p.getLastPathComponent();
newMenu.add(new JMenuItem(new AbstractAction(Messages.getString("DBTree.showExceptionDetails")) { //$NON-NLS-1$
                 public void actionPerformed(ActionEvent e) {
+                    Throwable firstException = ((SQLObject) node).
+ getChildrenInaccessibleReasons().entrySet().iterator().next().getValue(); SPSUtils.showExceptionDialogNoReport(session.getArchitectFrame(), - Messages.getString("DBTree.exceptionNodeReport"), node.getChildrenInaccessibleReason()); //$NON-NLS-1$ + Messages.getString("DBTree.exceptionNodeReport"), firstException); //$NON-NLS-1$
                 }
             }));

// If the sole child is an exception node, we offer the user a way to re-try the operation
-            if (node.getChildrenInaccessibleReason() != null) {
+            if (!node.getChildrenInaccessibleReasons().isEmpty()) {
newMenu.add(new JMenuItem(new AbstractAction(Messages.getString("DBTree.retryActionName")) { //$NON-NLS-1$
                     public void actionPerformed(ActionEvent e) {
                         node.setPopulated(false);
@@ -709,7 +715,7 @@
                    // start a thread to poke the new SQLDatabase object...
logger.debug("start poking database " + newDB.getName()); //$NON-NLS-1$
                    PokeDBWorker poker = new PokeDBWorker(newDB);
- new Thread(poker, "PokeDB: " + newDB.getName()).start(); //$NON-NLS-1$ + session.runInBackground(poker, "PokeDB: " + newDB.getName()); //$NON-NLS-1$
                } else {
JOptionPane.showMessageDialog(DBTree.this, Messages.getString("DBTree.cannotAddConnectionType", dbcs.getClass().toString()), Messages.getString("DBTree.cannotAddConnectionTypeTitle"), JOptionPane.INFORMATION_MESSAGE);
=======================================
--- /trunk/src/ca/sqlpower/architect/swingui/PlayPen.java Thu Mar 4 08:08:10 2010 +++ /trunk/src/ca/sqlpower/architect/swingui/PlayPen.java Wed Mar 10 08:21:50 2010
@@ -1493,24 +1493,45 @@
                public void doStuff() {
logger.info("AddObjectsTask starting on thread "+Thread.currentThread().getName()); //$NON-NLS-1$ session.getArchitectFrame().getContentPane().setCursor(new Cursor(Cursor.WAIT_CURSOR));
+
                        try {
-                               int tableCount = 0;
-
-                               Iterator<SQLObject> soIt = 
sqlObjects.iterator();
-                               // first pass: figure out how much work we need 
to do...
-                               while (soIt.hasNext() && !isCancelled()) {
-                                       SQLObject so = soIt.next();
-                    tableCount += SQLObjectUtils.countTablesSnapshot(so);
-                               }
-                               setJobSize(new Integer(tableCount));
-
-                               ensurePopulated(sqlObjects);
-
+                           Iterator<SQLObject> soIt = sqlObjects.iterator();
+                           // first pass: Cause all of the SQLObjects between 
the given
+                           // ones and the table descendents to populate...
+                           while (soIt.hasNext() && !isCancelled()) {
+                               SQLObject so = soIt.next();
+                               SQLObjectUtils.countTablesSnapshot(so);
+                           }
                        } catch (SQLObjectException e) {
-                               logger.error("Unexpected exception during 
populate", e); //$NON-NLS-1$
+ logger.error("Unexpected exception during populate", e); //$NON-NLS-1$
                 setDoStuffException(e);
- errorMessage = "Unexpected exception during populate: " + e.getMessage(); //$NON-NLS-1$
-                       }
+ errorMessage = "Unexpected exception during populate: " + e.getMessage(); //$NON-NLS-1$
+            }
+
+                       //Second pass: count the tables. Done in the foreground 
to
+                       //wait for the objects to be fully populated by pass 1.
+                       session.runInForeground(new Runnable() {
+                           public void run() {
+                               try {
+                                   int tableCount = 0;
+                                   Iterator<SQLObject> soIt = 
sqlObjects.iterator();
+                                   while (soIt.hasNext() && !isCancelled()) {
+                                       SQLObject so = soIt.next();
+                                       tableCount += 
SQLObjectUtils.countTablesSnapshot(so);
+                                   }
+                                   setJobSize(new Integer(tableCount));
+                               } catch (SQLObjectException e) {
+ logger.error("Unexpected exception, objects should be populated by " +
+                                               "this pass.", e); //$NON-NLS-1$
+                                   setDoStuffException(e);
+ errorMessage = "Unexpected exception, objects should be populated " +
+                                               "by this pass: " + 
e.getMessage(); //$NON-NLS-1$
+                               }
+                           }
+                       });
+
+                       ensurePopulated(sqlObjects);
+
                        logger.info("AddObjectsTask done"); //$NON-NLS-1$
                }

@@ -1525,7 +1546,15 @@
                private void ensurePopulated(List<? extends SQLObject> soList) {
                        for (SQLObject so : soList) {
                                if (isCancelled()) break;
-                               if (so instanceof SQLTable) 
setProgress(getProgress() + 1);
+                               if (so instanceof SQLTable) {
+ //pushing updates to foreground as population happens on the foreground + //and this will keep the progress bar more honest with what is happening.
+                                   session.runInForeground(new Runnable(){
+                        public void run() {
+                            setProgress(getProgress() + 1);
+                        }
+                    });
+                               }
                 ensurePopulated(so.getChildren());
                        }
                }
=======================================
--- /trunk/src/ca/sqlpower/architect/swingui/SwingUIProjectLoader.java Thu Mar 4 09:30:17 2010 +++ /trunk/src/ca/sqlpower/architect/swingui/SwingUIProjectLoader.java Wed Mar 10 08:21:50 2010
@@ -80,6 +80,7 @@
 import ca.sqlpower.sqlobject.SQLRelationship;
 import ca.sqlpower.sqlobject.SQLSchema;
 import ca.sqlpower.sqlobject.SQLTable;
+import ca.sqlpower.sqlobject.SQLRelationship.SQLImportedKey;
 import ca.sqlpower.util.ExceptionReport;
 import ca.sqlpower.util.SQLPowerUtils;
 import ca.sqlpower.util.UserPrompter;
@@ -1265,8 +1266,11 @@
propNames.put("name", o.getName()); // note: there was no name attrib for SQLDatabase, SQLRelationship.ColumnMapping, and SQLExceptionNode //$NON-NLS-1$
         propNames.put("UUID", o.getUUID());

-        if (o.getChildrenInaccessibleReason() != null) {
- propNames.put("sql-exception", o.getChildrenInaccessibleReason().getMessage()); //$NON-NLS-1$
+        if (!o.getChildrenInaccessibleReasons().isEmpty()) {
+            //Only storing the top exception to prevent file format changes
+ //Only the SQLTable should have multiple children inaccessible reasons. + Throwable topException = o.getChildrenInaccessibleReason(SQLObject.class);
+            propNames.put("sql-exception", topException); //$NON-NLS-1$
         }

         if (o instanceof SQLDatabase) {
@@ -1394,20 +1398,47 @@
             String indicesFolder = null;
             if (o instanceof SQLTable) {
                 SQLTable table = (SQLTable) o;
+                String exception;
+ if (table.getChildrenInaccessibleReason(SQLColumn.class) != null) {
+                    exception = "sql-exception=\"" +
+ table.getChildrenInaccessibleReason(SQLColumn.class) + "\" ";
+                } else {
+                    exception = "";
+                }
ioo.println(out, "<folder id=\"FOL" + id + "1\" populated=\"" + table.isColumnsPopulated() + "\" name=\"Columns\" " +
-                                       "physicalName=\"Columns\" type=\"1\">");
+ "physicalName=\"Columns\" " + exception + "type=\"1\">");
                 ioo.indent++;

+ if (table.getChildrenInaccessibleReason(SQLImportedKey.class) != null) {
+                    exception = "sql-exception=\"" +
+ table.getChildrenInaccessibleReason(SQLImportedKey.class) + "\" ";
+                } else {
+                    exception = "";
+                }
importedKeysFolder = "<folder id=\"FOL" + id + "2\" populated=\"" + table.isImportedKeysPopulated() + "\" name=\"Imported Keys\" " +
-                    "physicalName=\"Imported Keys\" type=\"2\">";
+ "physicalName=\"Imported Keys\" " + exception + "type=\"2\">";
+
+ if (table.getChildrenInaccessibleReason(SQLRelationship.class) != null) {
+                    exception = "sql-exception=\"" +
+ table.getChildrenInaccessibleReason(SQLRelationship.class) + "\" ";
+                } else {
+                    exception = "";
+                }
exportedKeysFolder = "<folder id=\"FOL" + id + "3\" populated=\"" + table.isExportedKeysPopulated() + "\" name=\"Exported Keys\" " +
-                    "physicalName=\"Exported Keys\" type=\"3\">";
+ "physicalName=\"Exported Keys\" " + exception + "type=\"3\">";
+
+ if (table.getChildrenInaccessibleReason(SQLIndex.class) != null) {
+                    exception = "sql-exception=\"" +
+ table.getChildrenInaccessibleReason(SQLIndex.class) + "\" ";
+                } else {
+                    exception = "";
+                }
indicesFolder = "<folder id=\"FOL" + id + "4\" populated=\"" +
                     table.isIndicesPopulated() + "\" name=\"Indices\" " +
-                    "physicalName=\"Indices\" type=\"4\">";
+ "physicalName=\"Indices\" " + exception + "type=\"4\">";
             }
             while (children.hasNext()) {
                 SQLObject child = (SQLObject) children.next();
=======================================
--- /trunk/src/ca/sqlpower/architect/swingui/dbtree/DBTreeCellRenderer.java Tue Dec 22 12:38:24 2009 +++ /trunk/src/ca/sqlpower/architect/swingui/dbtree/DBTreeCellRenderer.java Wed Mar 10 08:21:50 2010
@@ -147,14 +147,14 @@
                        setIcon(null);
                }

- if (value instanceof SQLObject && ((SQLObject) value).getChildrenInaccessibleReason() != null) { + if (value instanceof SQLObject && !((SQLObject) value).getChildrenInaccessibleReasons().isEmpty()) { logger.debug("Children are not accessible from the node " + ((SQLObject) value).getName());
             if (getIcon() == null) {
                 setIcon(ERROR_BADGE);
             } else {
setIcon(new ComposedIcon(Arrays.asList(getIcon(), ERROR_BADGE)));
             }
- setToolTipText("Inaccessible: " + ((SQLObject) value).getChildrenInaccessibleReason()); + setToolTipText("Inaccessible: " + ((SQLObject) value).getChildrenInaccessibleReasons());
         }

                this.selected = sel;
=======================================
--- /trunk/src/ca/sqlpower/architect/swingui/dbtree/DBTreeModel.java Thu Mar 4 14:42:47 2010 +++ /trunk/src/ca/sqlpower/architect/swingui/dbtree/DBTreeModel.java Wed Mar 10 08:21:50 2010
@@ -176,8 +176,26 @@
                 throw new RuntimeException(e);
             }
         }
+
+        @Override
+ public Throwable getChildrenInaccessibleReason(Class<? extends SQLObject> childType) { + if (childType == containingChildType || childType == SQLObject.class) { + return parentTable.getChildrenInaccessibleReason(containingChildType);
+            } else {
+                return null;
+            }
+        }

        }
+
+       /**
+        * For use in the {...@link DBTreeSPListener}.
+        */
+       private enum EventType {
+           INSERT,
+           REMOVE,
+           CHANGE
+       }

        /**
* A {...@link SPListener} implementation that will fire tree events as the underlying
@@ -185,7 +203,48 @@
         */
        private class DBTreeSPListener implements SPListener {

+           /**
+ * Small inner class to group the tree event with the type of event it is.
+            */
+           private class TreeEventWithType {
+               private final TreeModelEvent evt;
+               private final EventType type;
+
+               public TreeEventWithType(TreeModelEvent evt, EventType type) {
+                this.evt = evt;
+                this.type = type;
+               }
+
+               public TreeModelEvent getEvt() {
+                return evt;
+            }
+
+               public EventType getType() {
+                return type;
+            }
+
+               @Override
+               public String toString() {
+                   return evt.toString() + ", " + type;
+               }
+           }
+
+        /**
+ * List to hold all of the events. The events are to be pooled during a + * transaction and only acted upon when the transaction is complete. + * This gives methods like populate the ability to fire multiple child
+         * events but not cause the tree model to get the children of the
+ * objects it is looking at which would cause populate to start over
+         * again when it is already in the middle of populating.
+         */
+ private List<TreeEventWithType> allEvents = new ArrayList<TreeEventWithType>();
+
+           private int transactionCount = 0;
+
         public void childAdded(SPChildEvent e) {
+            if (!root.getSession().isForegroundThread())
+ throw new IllegalStateException("Adding a child " + e.getChild() + " to " + e.getSource() +
+                        " not on the foreground thread.");
             if (logger.isDebugEnabled()) {
logger.debug("dbChildrenInserted. source="+e.getSource() //$NON-NLS-1$
                         +" index: "+e.getIndex() //$NON-NLS-1$
@@ -197,7 +256,11 @@

             Set<TreeModelEvent> events = createTreeEvents(e);
             for (TreeModelEvent evt : events) {
-                fireTreeNodesInserted(evt);
+                if (transactionCount > 0) {
+ allEvents.add(new TreeEventWithType(evt, EventType.INSERT));
+                } else {
+                    fireTreeNodesInserted(evt);
+                }
             }

if (e.getChild() instanceof SQLTable && foldersInTables.get(e.getChild()) == null) {
@@ -208,8 +271,13 @@
                 for (int i = 0; i < folderList.size(); i++) {
                     positions[i] = i;
                 }
- fireTreeNodesInserted(new TreeModelEvent(table, getPathToNode(table),
-                        positions, folderList.toArray()));
+ final TreeModelEvent evt = new TreeModelEvent(table, getPathToNode(table),
+                        positions, folderList.toArray());
+                if (transactionCount > 0) {
+ allEvents.add(new TreeEventWithType(evt, EventType.INSERT));
+                } else {
+                    fireTreeNodesInserted(evt);
+                }
             } else {
                 setupTreeForNode((SQLObject) e.getChild());
             }
@@ -217,6 +285,9 @@
         }

         public void childRemoved(SPChildEvent e) {
+            if (!root.getSession().isForegroundThread())
+ throw new IllegalStateException("Removing a child " + e.getChild() + " to " + e.getSource() +
+                        " not on the foreground thread.");
             if (logger.isDebugEnabled()) {
logger.debug("dbchildrenremoved. source="+e.getSource() //$NON-NLS-1$
                         +" index: "+e.getIndex() //$NON-NLS-1$
@@ -230,23 +301,58 @@

             Set<TreeModelEvent> events = createTreeEvents(e);
             for (TreeModelEvent evt : events) {
-                fireTreeNodesRemoved(evt);
+                if (transactionCount > 0) {
+ allEvents.add(new TreeEventWithType(evt, EventType.REMOVE));
+                } else {
+                    fireTreeNodesRemoved(evt);
+                }
             }
         }

         public void transactionEnded(TransactionEvent e) {
-            //do nothing
+            if (!root.getSession().isForegroundThread())
+ throw new IllegalStateException("Transaction ended for " + e.getSource() +
+                        " while not on the foreground thread.");
+            if (transactionCount == 0) {
+ throw new IllegalStateException("Transaction ended outside of a transaction.");
+            }
+            transactionCount--;
+            if (transactionCount == 0) {
+ List<TreeEventWithType> currentEvents = new ArrayList<TreeEventWithType>(allEvents);
+                for (TreeEventWithType evt : currentEvents) {
+                    if (evt.getType() == EventType.INSERT) {
+                        fireTreeNodesInserted(evt.getEvt());
+                    } else if (evt.getType() == EventType.REMOVE) {
+                        fireTreeNodesRemoved(evt.getEvt());
+                    } else if (evt.getType() == EventType.CHANGE) {
+                        fireTreeNodesChanged(evt.getEvt());
+                    } else {
+ throw new IllegalStateException("Unknown event type " + evt.getType());
+                    }
+                }
+                allEvents.clear();
+            }
         }

         public void transactionRollback(TransactionEvent e) {
-            //do nothing
+            if (!root.getSession().isForegroundThread())
+ throw new IllegalStateException("Transaction rolled back for " + e.getSource() +
+                        " while not on the foreground thread.");
+            transactionCount = 0;
+            allEvents.clear();
         }

         public void transactionStarted(TransactionEvent e) {
-            //do nothing
+            if (!root.getSession().isForegroundThread())
+ throw new IllegalStateException("Transaction started for " + e.getSource() +
+                        " while not on the foreground thread.");
+            transactionCount++;
         }

         public void propertyChanged(PropertyChangeEvent e) {
+            if (!root.getSession().isForegroundThread())
+ throw new IllegalStateException("Changing the property" + e.getPropertyName() + " on " + e.getSource() +
+                        " not on the foreground thread.");
logger.debug("dbObjectChanged. source="+e.getSource()); //$NON-NLS-1$ if ((!SwingUtilities.isEventDispatchThread()) && (!refireOnAnyThread)) { logger.warn("Not refiring because this is not the EDT. You will need to call refreshTreeStructure() at some point in the future."); //$NON-NLS-1$
@@ -291,7 +397,12 @@
                 refreshTreeStructure();
logger.info("Changing a UUID. This should only be done during load.");
             } else {
- fireTreeNodesChanged(new TreeModelEvent(this, getPathToNode(source))); + final TreeModelEvent evt = new TreeModelEvent(this, getPathToNode(source));
+                if (transactionCount > 0) {
+ allEvents.add(new TreeEventWithType(evt, EventType.CHANGE));
+                } else {
+                    fireTreeNodesChanged(evt);
+                }
             }
         }

@@ -382,7 +493,7 @@

                SQLObject sqlParent = (SQLObject) parent;
                try {
- if (logger.isDebugEnabled()) logger.debug("returning "+sqlParent.getChildrenWithoutPopulating().size()); //$NON-NLS-1$ + if (logger.isDebugEnabled()) logger.debug("returning "+sqlParent.getChildren().size()); //$NON-NLS-1$
                        return sqlParent.getChildren().size();
                } catch (Exception e) {
                    throw new RuntimeException(e);
@@ -402,16 +513,17 @@
        }

        public int getIndexOfChild(Object parent, Object child) {
- if (logger.isDebugEnabled()) logger.debug("DBTreeModel.getIndexOfChild("+parent+","+child+"): returning "+((SQLObject) parent).getChildren().indexOf(child)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+           SPObject spChild = (SPObject) child;
+ if (logger.isDebugEnabled()) logger.debug("DBTreeModel.getIndexOfChild("+parent+","+child+"): returning "+((SQLObject) parent).getChildren(spChild.getClass()).indexOf(child)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

                if (parent instanceof FolderNode) {
-            return ((FolderNode) parent).getChildren().indexOf(child);
+ return ((FolderNode) parent).getChildren(spChild.getClass()).indexOf(child);
         } else if (parent instanceof SQLTable) {
             if (foldersInTables.get((SQLTable) parent) == null) return -1;
             return foldersInTables.get((SQLTable) parent).indexOf(child);
         }

- return ((SQLObject) parent).getChildrenWithoutPopulating().indexOf(child); + return ((SQLObject) parent).getChildren(spChild.getClass()).indexOf(child);
        }

        // -------------- treeModel event source support -----------------

Reply via email to