This is an automated email from the ASF dual-hosted git repository.

ntimofeev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git


The following commit(s) were added to refs/heads/master by this push:
     new 69f1bf066 Drag and drop in DbImport targetTree (#622)
69f1bf066 is described below

commit 69f1bf066ec1f846db8ed1af919d2793c1bac0be
Author: Mikhail Dzianishchyts <mikhail.dzianishch...@gmail.com>
AuthorDate: Fri Aug 2 14:56:13 2024 +0300

    Drag and drop in DbImport targetTree (#622)
    
    * sorting only by type
    
    * migrate from TreeSet to List(to prevent auto sorting patterns), tests 
added
    
    * sort button functionality
    
    * modified equals in TableFilter
    
    * cleanUp
    
    * setDirty after sorting added
    
    * drag and drop in DbImport target tree
    
    * fix bugs, refactoring
    
    * fixed bug with moving node on the parent
    
    * Use interfaces for args and returns
    
    * Keep expanded nodes on model reload
    
    * Add dummy node to fix drag and drop issues
    
    * Check for duplicates on drag and drop
    
    * Clean up
    
    * Update tests
    
    ---------
    
    Co-authored-by: Ivan Nikitka 
<70625960+ivan-niki...@users.noreply.github.com>
    Co-authored-by: Ivan Nikitka <nikitko.i...@gmail.com>
---
 .../dbsync/reverse/dbimport/PatternParam.java      |  27 ++--
 .../reverse/filters/FiltersConfigBuilder.java      |  10 +-
 .../dbsync/reverse/filters/IncludeTableFilter.java |  21 ++-
 .../dbsync/reverse/filters/PatternFilter.java      |  43 ++++--
 .../dbsync/reverse/filters/TableFilter.java        |  37 +++--
 .../cayenne/dbsync/reverse/dbload/DbLoaderIT.java  |  43 ++++++
 .../dbsync/reverse/filters/FiltersConfigTest.java  | 134 +++++++++++++++++-
 .../dbsync/reverse/filters/TableFilterTest.java    |  55 ++++++--
 .../tools/DbImporterMojoConfigurationTest.java     |   7 +-
 .../modeler/action/DefaultActionManager.java       |   3 +
 .../cayenne/modeler/action/SortNodesAction.java    |  59 ++++++++
 .../modeler/action/dbimport/DeleteNodeAction.java  |   6 +-
 .../action/dbimport/DragAndDropNodeAction.java     | 157 +++++++++++++++++++++
 .../modeler/action/dbimport/EditNodeAction.java    |   4 +-
 .../action/dbimport/MoveImportNodeAction.java      |   8 +-
 .../action/dbimport/TreeManipulationAction.java    |   2 +-
 .../modeler/dialog/db/load/DbImportTreeNode.java   |  60 +++++---
 .../modeler/dialog/db/load/TransferableNode.java   |   5 +-
 .../modeler/editor/dbimport/DbImportSorter.java    |  22 +--
 .../modeler/editor/dbimport/DbImportTree.java      |  46 ++++--
 .../editor/dbimport/DbImportTreeCellEditor.java    |   7 +-
 .../editor/dbimport/DbImportTreeCellRenderer.java  |  54 ++++++-
 .../editor/dbimport/DraggableTreePanel.java        | 121 +++++++++++++---
 .../editor/dbimport/PrintColumnsBiFunction.java    |   2 +-
 .../editor/dbimport/PrintTablesBiFunction.java     |   3 +-
 .../modeler/editor/dbimport/TreeToolbarPanel.java  |  40 ++++--
 .../modeler/undo/DbImportTreeUndoableEdit.java     |   6 +-
 .../cayenne/modeler/images/icon-dbi-sort.png       | Bin 0 -> 315 bytes
 .../editor/dbimport/DbImportSorterTest.java        |  40 ++++--
 29 files changed, 848 insertions(+), 174 deletions(-)

diff --git 
a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbimport/PatternParam.java
 
b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbimport/PatternParam.java
index 598bd132d..b62beb4e2 100644
--- 
a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbimport/PatternParam.java
+++ 
b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbimport/PatternParam.java
@@ -19,6 +19,8 @@
 
 package org.apache.cayenne.dbsync.reverse.dbimport;
 
+import java.util.Objects;
+
 import static org.apache.cayenne.util.Util.isBlank;
 
 /**
@@ -77,16 +79,6 @@ public class PatternParam {
         set(pattern.getName());
     }
 
-    @Override
-    public String toString() {
-        return toString(new StringBuilder(), "").toString();
-    }
-
-    public StringBuilder toString(StringBuilder res, String s) {
-        res.append(s).append(getClass().getSimpleName()).append(": 
").append(pattern).append("\n");
-        return res;
-    }
-
     @Override
     public boolean equals(Object obj) {
         if (obj == this) {
@@ -101,4 +93,19 @@ public class PatternParam {
         PatternParam patternParam = (PatternParam) obj;
         return patternParam.getPattern().equals(pattern);
     }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(pattern);
+    }
+
+    @Override
+    public String toString() {
+        return toString(new StringBuilder(), "").toString();
+    }
+
+    public StringBuilder toString(StringBuilder res, String s) {
+        res.append(s).append(getClass().getSimpleName()).append(": 
").append(pattern).append("\n");
+        return res;
+    }
 }
diff --git 
a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/FiltersConfigBuilder.java
 
b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/FiltersConfigBuilder.java
index aca90baf3..8303c960b 100644
--- 
a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/FiltersConfigBuilder.java
+++ 
b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/FiltersConfigBuilder.java
@@ -27,8 +27,6 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.SortedSet;
-import java.util.TreeSet;
 import java.util.regex.Pattern;
 
 import org.apache.cayenne.dba.DbAdapter;
@@ -156,16 +154,16 @@ public final class FiltersConfigBuilder {
         return schemaFilters;
     }
 
-    private SortedSet<Pattern> transformExcludeTable(Collection<ExcludeTable> 
excludeTables) {
-        SortedSet<Pattern> res = new 
TreeSet<>(PatternFilter.PATTERN_COMPARATOR);
+    private List<Pattern> transformExcludeTable(Collection<ExcludeTable> 
excludeTables) {
+        List<Pattern> res =  new ArrayList<>();
         for (ExcludeTable exclude : excludeTables) {
             res.add(PatternFilter.pattern(exclude.getPattern()));
         }
         return res;
     }
 
-    private SortedSet<IncludeTableFilter> 
transformIncludeTable(Collection<IncludeTable> includeTables) {
-        SortedSet<IncludeTableFilter> includeTableFilters = new TreeSet<>();
+    private List<IncludeTableFilter> 
transformIncludeTable(Collection<IncludeTable> includeTables) {
+        List<IncludeTableFilter> includeTableFilters = new ArrayList<>();
         for (IncludeTable includeTable : includeTables) {
             includeTableFilters.add(new 
IncludeTableFilter(includeTable.getPattern()
                     , transform(includeTable.getIncludeColumns(), 
includeTable.getExcludeColumns())
diff --git 
a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/IncludeTableFilter.java
 
b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/IncludeTableFilter.java
index 35d504f5b..670b63823 100644
--- 
a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/IncludeTableFilter.java
+++ 
b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/IncludeTableFilter.java
@@ -18,11 +18,12 @@
  ****************************************************************/
 package org.apache.cayenne.dbsync.reverse.filters;
 
+import java.util.Objects;
 import java.util.regex.Pattern;
 
 /**
-* @since 4.0.
-*/
+ * @since 4.0.
+ */
 public class IncludeTableFilter implements Comparable<IncludeTableFilter> {
     public final Pattern pattern;
 
@@ -50,14 +51,14 @@ public class IncludeTableFilter implements 
Comparable<IncludeTableFilter> {
         this.relationshipFilter = relationshipFilter;
     }
 
-    public boolean isIncludeColumn (String name) {
+    public boolean isIncludeColumn(String name) {
         return columnsFilter.isIncluded(name);
     }
 
     /**
      * @since 4.1
      */
-    public boolean isIncludeRelationship (String name) {
+    public boolean isIncludeRelationship(String name) {
         return relationshipFilter.isIncluded(name);
     }
 
@@ -75,6 +76,18 @@ public class IncludeTableFilter implements 
Comparable<IncludeTableFilter> {
 
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        IncludeTableFilter that = (IncludeTableFilter) o;
+
+        if (!Objects.equals(pattern, that.pattern)) return false;
+        if (!Objects.equals(columnsFilter, that.columnsFilter)) return false;
+        return Objects.equals(relationshipFilter, that.relationshipFilter);
+    }
+
     @Override
     public String toString() {
         return toString(new StringBuilder(), "").toString();
diff --git 
a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/PatternFilter.java
 
b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/PatternFilter.java
index c06861903..704c14a2f 100644
--- 
a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/PatternFilter.java
+++ 
b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/PatternFilter.java
@@ -20,9 +20,9 @@ package org.apache.cayenne.dbsync.reverse.filters;
 
 import org.apache.cayenne.util.Util;
 
+import java.util.ArrayList;
 import java.util.Comparator;
-import java.util.SortedSet;
-import java.util.TreeSet;
+import java.util.List;
 import java.util.regex.Pattern;
 
 /**
@@ -63,18 +63,22 @@ public class PatternFilter {
         }
     };
 
-    private final SortedSet<Pattern> includes;
-    private final SortedSet<Pattern> excludes;
+    private final List<Pattern> includes;
+    private final List<Pattern> excludes;
 
     public PatternFilter() {
-        this.includes = new TreeSet<>(PATTERN_COMPARATOR);
-        this.excludes = new TreeSet<>(PATTERN_COMPARATOR);
+        this.includes = new ArrayList<>();
+        this.excludes = new ArrayList<>();
     }
 
-    public SortedSet<Pattern> getIncludes() {
+    public List<Pattern> getIncludes() {
         return includes;
     }
 
+    public List<Pattern> getExcludes() {
+        return excludes;
+    }
+
     public PatternFilter include(Pattern p) {
         includes.add(p);
 
@@ -132,14 +136,29 @@ public class PatternFilter {
             return true;
         }
 
-        if (o == null || getClass() != o.getClass()) {
+        if (!(o instanceof PatternFilter)) {
             return false;
         }
 
+        PatternFilter that = (PatternFilter) o;
+
+        if (includes == that.includes) {
+            return true;
+        }
+
+        if (includes.size() != that.includes.size()) {
+            return false;
+        }
 
-        PatternFilter filter = (PatternFilter) o;
-        return includes.equals(filter.includes)
-                && excludes.equals(filter.excludes);
+        // Check if the lists have the same patterns in the same order
+        for (int i = 0; i < includes.size(); i++) {
+            Pattern pattern = excludes.get(i);
+            Pattern thatPattern = that.excludes.get(i);
+            if (!pattern.pattern().equals(thatPattern.pattern())) {
+                return false;
+            }
+        }
+        return true;
     }
 
     @Override
@@ -153,7 +172,7 @@ public class PatternFilter {
         } else if (includes.size() > 1) {
             res.append("(").append(Util.join(includes, " OR ")).append(")");
         } else {
-            res.append(includes.first().pattern());
+            res.append(includes.get(0).pattern());
         }
 
         if (!excludes.isEmpty()) {
diff --git 
a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/TableFilter.java
 
b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/TableFilter.java
index c403ba0ef..7b7f6d80c 100644
--- 
a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/TableFilter.java
+++ 
b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/TableFilter.java
@@ -19,7 +19,9 @@
 package org.apache.cayenne.dbsync.reverse.filters;
 
 
-import java.util.SortedSet;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
 import java.util.TreeSet;
 import java.util.regex.Pattern;
 
@@ -30,13 +32,13 @@ import org.apache.cayenne.util.Util;
  */
 public class TableFilter {
 
-    private final SortedSet<IncludeTableFilter> includes;
-    private final SortedSet<Pattern> excludes;
+    private final List<IncludeTableFilter> includes;
+    private final List<Pattern> excludes;
 
     /**
      * Includes can contain only one include table
      */
-    public TableFilter(SortedSet<IncludeTableFilter> includes, 
SortedSet<Pattern> excludes) {
+    public TableFilter(List<IncludeTableFilter> includes, List<Pattern> 
excludes) {
         this.includes = includes;
         this.excludes = excludes;
     }
@@ -89,22 +91,26 @@ public class TableFilter {
         return include;
     }
 
-    public SortedSet<IncludeTableFilter> getIncludes() {
+    public List<IncludeTableFilter> getIncludes() {
         return includes;
     }
 
+    public List<Pattern> getExcludes() {
+        return excludes;
+    }
+
     public static TableFilter include(String tablePattern) {
-        TreeSet<IncludeTableFilter> includes = new TreeSet<>();
+        List<IncludeTableFilter> includes = new ArrayList<>();
         includes.add(new IncludeTableFilter(tablePattern == null ? null : 
tablePattern.replaceAll("%", ".*")));
 
-        return new TableFilter(includes, new TreeSet<>());
+        return new TableFilter(includes, new ArrayList<>());
     }
 
     public static TableFilter everything() {
-        TreeSet<IncludeTableFilter> includes = new TreeSet<>();
+        List<IncludeTableFilter> includes = new ArrayList<>();
         includes.add(new IncludeTableFilter(null));
 
-        return new TableFilter(includes, new TreeSet<>());
+        return new TableFilter(includes, new ArrayList<>());
     }
 
     protected StringBuilder toString(StringBuilder res, String prefix) {
@@ -133,9 +139,18 @@ public class TableFilter {
 
         TableFilter that = (TableFilter) o;
 
-        return excludes.equals(that.excludes)
-                && includes.equals(that.includes);
+        boolean excludeEquals = true;
+        // Check if the lists have the same patterns in the same order
+        for (int i = 0; i < excludes.size(); i++) {
+            Pattern pattern = excludes.get(i);
+            Pattern thatPattern = that.excludes.get(i);
+            if (!pattern.pattern().equals(thatPattern.pattern())) {
+                excludeEquals = false;
+                break;
+            }
+        }
 
+        return includes.equals(that.includes) && excludeEquals;
     }
 
     @Override
diff --git 
a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/dbload/DbLoaderIT.java
 
b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/dbload/DbLoaderIT.java
index 4bf07fb10..39303bce9 100644
--- 
a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/dbload/DbLoaderIT.java
+++ 
b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/dbload/DbLoaderIT.java
@@ -22,6 +22,12 @@ package org.apache.cayenne.dbsync.reverse.dbload;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dbsync.naming.DefaultObjectNameGenerator;
 import org.apache.cayenne.dbsync.naming.NoStemStemmer;
+import org.apache.cayenne.dbsync.reverse.dbimport.ExcludeColumn;
+import org.apache.cayenne.dbsync.reverse.dbimport.IncludeTable;
+import org.apache.cayenne.dbsync.reverse.dbimport.ReverseEngineering;
+import org.apache.cayenne.dbsync.reverse.dbimport.Schema;
+import org.apache.cayenne.dbsync.reverse.filters.FiltersConfig;
+import org.apache.cayenne.dbsync.reverse.filters.FiltersConfigBuilder;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.DbAttribute;
@@ -42,6 +48,7 @@ import java.sql.Connection;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 /**
@@ -67,6 +74,38 @@ public class DbLoaderIT extends RuntimeCase {
     private Connection connection;
 
 
+    @Test
+    public void testLoadingOrder() throws Exception {
+        ReverseEngineering engineering = new ReverseEngineering();
+        IncludeTable artistTableWithExclusion = new IncludeTable("ARTIST");
+        artistTableWithExclusion.addExcludeColumn(new 
ExcludeColumn("DATE_OF_BIRTH"));
+        engineering.addIncludeTable(artistTableWithExclusion);
+        engineering.addIncludeTable(new IncludeTable("ARTIST"));
+
+        IncludeTable paintingTableWithExclusion = new IncludeTable("PAINTING");
+        paintingTableWithExclusion.addExcludeColumn(new 
ExcludeColumn("PAINTING_DESCRIPTION"));
+        engineering.addIncludeTable(new IncludeTable("PAINTING"));
+        engineering.addIncludeTable(paintingTableWithExclusion);
+
+        FiltersConfigBuilder configBuilder = new 
FiltersConfigBuilder(engineering);
+        FiltersConfig filtersConfig = configBuilder.build();
+
+        DbLoaderConfiguration dbLoaderConfiguration = new 
DbLoaderConfiguration();
+        dbLoaderConfiguration.setFiltersConfig(filtersConfig);
+
+        DbLoader loader = createDbLoader(dbLoaderConfiguration);
+        DataMap loaded = loader.load();
+        assertNotNull(loaded);
+
+        DbEntity artist = loaded.getDbEntity("ARTIST");
+        DbEntity painting = loaded.getDbEntity("PAINTING");
+        assertNotNull(artist);
+        assertNotNull(painting);
+        assertNull(getDbAttribute(artist,"DATE_OF_BIRTH"));
+        assertNotNull(getDbAttribute(painting,"PAINTING_DESCRIPTION"));
+    }
+
+
     /**
      * Test that parts of loader are in place
      */
@@ -124,6 +163,10 @@ public class DbLoaderIT extends RuntimeCase {
         return new DbLoader(adapter, connection, CONFIG, null, new 
DefaultObjectNameGenerator(NoStemStemmer.getInstance()));
     }
 
+    private DbLoader createDbLoader(DbLoaderConfiguration configuration) {
+        return new DbLoader(adapter, connection, configuration, null, new 
DefaultObjectNameGenerator(NoStemStemmer.getInstance()));
+    }
+
     @After
     public void after() throws Exception {
         connection.close();
diff --git 
a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/FiltersConfigTest.java
 
b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/FiltersConfigTest.java
index e947b31c0..da277837f 100644
--- 
a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/FiltersConfigTest.java
+++ 
b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/FiltersConfigTest.java
@@ -20,14 +20,60 @@
 package org.apache.cayenne.dbsync.reverse.filters;
 
 import junit.framework.TestCase;
+import org.apache.cayenne.dbsync.reverse.dbimport.Catalog;
+import org.apache.cayenne.dbsync.reverse.dbimport.ExcludeColumn;
+import org.apache.cayenne.dbsync.reverse.dbimport.ExcludeProcedure;
+import org.apache.cayenne.dbsync.reverse.dbimport.ExcludeTable;
+import org.apache.cayenne.dbsync.reverse.dbimport.IncludeColumn;
+import org.apache.cayenne.dbsync.reverse.dbimport.IncludeProcedure;
+import org.apache.cayenne.dbsync.reverse.dbimport.IncludeTable;
+import org.apache.cayenne.dbsync.reverse.dbimport.ReverseEngineering;
+import org.apache.cayenne.dbsync.reverse.dbimport.Schema;
 
+import java.util.ArrayList;
 import java.util.Collections;
-import java.util.SortedSet;
-import java.util.TreeSet;
+import java.util.List;
 import java.util.regex.Pattern;
 
+import static org.junit.Assert.assertThrows;
+
 public class FiltersConfigTest extends TestCase {
 
+    private FiltersConfig filtersConfig;
+
+    @Override
+    protected void setUp() throws Exception {
+        ReverseEngineering engineering = new ReverseEngineering();
+        Catalog catalog = new Catalog("catalog");
+        Schema schema = new Schema("schema");
+
+        schema.addIncludeTable(new IncludeTable("iT2"));
+        schema.addIncludeTable(new IncludeTable("iT1"));
+
+        schema.addExcludeTable(new ExcludeTable("eT2"));
+        schema.addExcludeTable(new ExcludeTable("eT1"));
+
+        schema.addIncludeProcedure(new IncludeProcedure("iP2"));
+        schema.addIncludeProcedure(new IncludeProcedure("iP1"));
+
+        schema.addExcludeProcedure(new ExcludeProcedure("eP2"));
+        schema.addExcludeProcedure(new ExcludeProcedure("eP1"));
+
+        schema.addIncludeColumn(new IncludeColumn("iC2"));
+        schema.addIncludeColumn(new IncludeColumn("iC1"));
+
+        schema.addExcludeColumn(new ExcludeColumn("eC2"));
+        schema.addExcludeColumn(new ExcludeColumn("eC1"));
+
+        catalog.addSchema(schema);
+        engineering.addCatalog(catalog);
+
+        FiltersConfigBuilder configBuilder = new 
FiltersConfigBuilder(engineering);
+        configBuilder.compact();
+
+        filtersConfig = configBuilder.build();
+    }
+
     public void testToString_01() {
         FiltersConfig config = FiltersConfig.create(null, null,
                 TableFilter.everything(), PatternFilter.INCLUDE_EVERYTHING);
@@ -75,16 +121,92 @@ public class FiltersConfigTest extends TestCase {
                      "    Procedures: NONE\n", config.toString());
     }
 
-    private SortedSet<Pattern> excludes(String ... p) {
-        SortedSet<Pattern> patterns = new 
TreeSet<Pattern>(PatternFilter.PATTERN_COMPARATOR);
+    public void testTableFilter(){
+        assertNull(filtersConfig.tableFilter(null,null));
+
+        TableFilter tableFilter = filtersConfig.tableFilter("catalog", 
"schema");
+        assertNotNull(tableFilter);
+        assertEquals(2,tableFilter.getIncludes().size());
+        assertEquals(2,tableFilter.getExcludes().size());
+    }
+
+    public void testProceduresFilter(){
+        assertNull(filtersConfig.proceduresFilter(null,null));
+
+        PatternFilter patternFilter = 
filtersConfig.proceduresFilter("catalog", "schema");
+        assertNotNull(patternFilter);
+        assertEquals(2,patternFilter.getIncludes().size());
+        assertEquals(2,patternFilter.getExcludes().size());
+    }
+
+    public void testGetCatalogs(){
+        CatalogFilter[] catalogs = filtersConfig.getCatalogs();
+        assertNotNull(catalogs);
+        assertEquals(1,catalogs.length);
+    }
+
+    public void testGetCatalog(){
+        assertNull(filtersConfig.getCatalog(null));
+        CatalogFilter catalog = filtersConfig.getCatalog("catalog");
+        assertNotNull(catalog);
+        assertEquals("catalog",catalog.name);
+        assertEquals(1,catalog.schemas.length);
+    }
+
+    public void testGetSchemaFilter()  {
+        assertNull(filtersConfig.getSchemaFilter(null,null));
+
+        SchemaFilter schemaFilter = filtersConfig.getSchemaFilter("catalog", 
"schema");
+        assertNotNull(schemaFilter);
+        assertEquals("schema",schemaFilter.name);
+        assertEquals(2,schemaFilter.procedures.getIncludes().size());
+        assertEquals(2,schemaFilter.procedures.getExcludes().size());
+    }
+
+    public void testNullArgumentBuild() {
+        FiltersConfigBuilder configBuilder = new FiltersConfigBuilder(null);
+        assertThrows(NullPointerException.class, configBuilder::build);
+    }
+
+    public void testKeepingOrder()  {
+        SchemaFilter schemaFilter = 
filtersConfig.getCatalog("catalog").getSchema("schema");
+
+        List<IncludeTableFilter> tablesIncludes = 
schemaFilter.tables.getIncludes();
+        List<Pattern> includeColumns = 
tablesIncludes.get(0).columnsFilter.getIncludes();
+        List<Pattern> excludeColumns = 
tablesIncludes.get(0).columnsFilter.getExcludes();
+        List<Pattern> tablesExcludes = schemaFilter.tables.getExcludes();
+        List<Pattern> proceduresIncludes = 
schemaFilter.procedures.getIncludes();
+        List<Pattern> proceduresExcludes = 
schemaFilter.procedures.getExcludes();
+
+        assertEquals("iT2", tablesIncludes.get(0).pattern.pattern());
+        assertEquals("iT1", tablesIncludes.get(1).pattern.pattern());
+
+        assertEquals("eT2", tablesExcludes.get(0).pattern());
+        assertEquals("eT1", tablesExcludes.get(1).pattern());
+
+        assertEquals("iC2", includeColumns.get(0).pattern());
+        assertEquals("iC1", includeColumns.get(1).pattern());
+
+        assertEquals("eC2", excludeColumns.get(0).pattern());
+        assertEquals("eC1", excludeColumns.get(1).pattern());
+
+        assertEquals("iP2", proceduresIncludes.get(0).pattern());
+        assertEquals("iP1", proceduresIncludes.get(1).pattern());
+
+        assertEquals("eP2", proceduresExcludes.get(0).pattern());
+        assertEquals("eP1", proceduresExcludes.get(1).pattern());
+    }
+
+    private List<Pattern> excludes(String ... p) {
+        List<Pattern> patterns = new ArrayList<Pattern>();
         for (String pattern : p) {
             patterns.add(PatternFilter.pattern(pattern));
         }
         return patterns;
     }
 
-    protected SortedSet<IncludeTableFilter> includes(IncludeTableFilter ... 
filters) {
-        SortedSet<IncludeTableFilter> includeTableFilters = new 
TreeSet<IncludeTableFilter>();
+    protected List<IncludeTableFilter> includes(IncludeTableFilter ... 
filters) {
+        List<IncludeTableFilter> includeTableFilters = new ArrayList<>();
         Collections.addAll(includeTableFilters, filters);
 
         return includeTableFilters;
diff --git 
a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/TableFilterTest.java
 
b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/TableFilterTest.java
index 21b0e2179..a7a633c84 100644
--- 
a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/TableFilterTest.java
+++ 
b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/TableFilterTest.java
@@ -18,6 +18,8 @@
  ****************************************************************/
 package org.apache.cayenne.dbsync.reverse.filters;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.TreeSet;
 import java.util.regex.Pattern;
 
@@ -39,11 +41,11 @@ public class TableFilterTest {
 
     @Test
     public void testInclude() {
-        TreeSet<IncludeTableFilter> includes = new TreeSet<>();
+        List<IncludeTableFilter> includes = new ArrayList<>();
         includes.add(new IncludeTableFilter("aaa"));
         includes.add(new IncludeTableFilter("bb"));
 
-        TableFilter filter = new TableFilter(includes, new 
TreeSet<>(PatternFilter.PATTERN_COMPARATOR));
+        TableFilter filter = new TableFilter(includes, new ArrayList<>());
 
         assertTrue(filter.isIncludeTable("aaa"));
         assertFalse(filter.isIncludeTable("aa"));
@@ -56,11 +58,11 @@ public class TableFilterTest {
 
     @Test
     public void testExclude() {
-        TreeSet<Pattern> excludes = new 
TreeSet<>(PatternFilter.PATTERN_COMPARATOR);
+        List<Pattern> excludes = new ArrayList<>();
         excludes.add(Pattern.compile("aaa"));
         excludes.add(Pattern.compile("bb"));
 
-        TreeSet<IncludeTableFilter> includes = new TreeSet<>();
+        List<IncludeTableFilter> includes = new ArrayList<>();
         includes.add(new IncludeTableFilter(null, 
PatternFilter.INCLUDE_EVERYTHING));
 
         TableFilter filter = new TableFilter(includes, excludes);
@@ -76,11 +78,11 @@ public class TableFilterTest {
 
     @Test
     public void testIncludeExclude() {
-        TreeSet<Pattern> excludes = new 
TreeSet<>(PatternFilter.PATTERN_COMPARATOR);
+        List<Pattern> excludes = new ArrayList<>();
         excludes.add(Pattern.compile("aaa"));
         excludes.add(Pattern.compile("bb"));
 
-        TreeSet<IncludeTableFilter> includes = new TreeSet<>();
+        List<IncludeTableFilter> includes = new ArrayList<>();
         includes.add(new IncludeTableFilter("aa.*"));
 
         TableFilter filter = new TableFilter(includes, excludes);
@@ -96,11 +98,11 @@ public class TableFilterTest {
 
     @Test
     public void testGetTableFilter() {
-        TreeSet<IncludeTableFilter> includes = new 
TreeSet<IncludeTableFilter>();
+        List<IncludeTableFilter> includes = new ArrayList<>();
         includes.add(new IncludeTableFilter("aaa"));
         includes.add(new IncludeTableFilter("bb"));
 
-        TreeSet<Pattern> excludes = new TreeSet<>();
+        List<Pattern> excludes = new ArrayList<>();
 
         TableFilter filter = new TableFilter(includes, excludes);
 
@@ -112,4 +114,41 @@ public class TableFilterTest {
         assertNull(filter.getIncludeTableColumnFilter(""));
         assertNull(filter.getIncludeTableColumnFilter("bbbb"));
     }
+
+    @Test
+    public void testExcludePriority(){
+        List<IncludeTableFilter> includes = new ArrayList<>();
+        includes.add(new IncludeTableFilter("a"));
+
+        List<Pattern> excludes = new ArrayList<>();
+        excludes.add(Pattern.compile("a"));
+
+        TableFilter tableFilter = new TableFilter(includes, excludes);
+
+        assertNull( tableFilter.getIncludeTableColumnFilter("a"));
+    }
+
+    @Test
+    public void testPatternsOrder(){
+        List<IncludeTableFilter> includes = new ArrayList<>();
+        includes.add(new IncludeTableFilter("b"));
+        includes.add(new IncludeTableFilter("a"));
+
+        List<Pattern> excludes = new ArrayList<>();
+        excludes.add(Pattern.compile("b"));
+        excludes.add(Pattern.compile("a"));
+
+        TableFilter tableFilter = new TableFilter(includes, excludes);
+
+        assertEquals("b",tableFilter.getIncludes().get(0).pattern.pattern());
+        assertEquals("b",tableFilter.getExcludes().get(0).pattern());
+    }
+    @Test
+    public void testNullArguments(){
+        TableFilter tableFilter = new TableFilter(null, null);
+        assertNotNull(tableFilter);
+        assertThrows(NullPointerException.class, () -> 
tableFilter.isIncludeTable(null)  );
+
+    }
+
 }
\ No newline at end of file
diff --git 
a/maven-plugins/cayenne-maven-plugin/src/test/java/org/apache/cayenne/tools/DbImporterMojoConfigurationTest.java
 
b/maven-plugins/cayenne-maven-plugin/src/test/java/org/apache/cayenne/tools/DbImporterMojoConfigurationTest.java
index 6ca8ef2ac..d2c056379 100644
--- 
a/maven-plugins/cayenne-maven-plugin/src/test/java/org/apache/cayenne/tools/DbImporterMojoConfigurationTest.java
+++ 
b/maven-plugins/cayenne-maven-plugin/src/test/java/org/apache/cayenne/tools/DbImporterMojoConfigurationTest.java
@@ -18,9 +18,10 @@
  ****************************************************************/
 package org.apache.cayenne.tools;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
-import java.util.TreeSet;
 import java.util.regex.Pattern;
 
 import org.apache.cayenne.dbsync.reverse.dbimport.Catalog;
@@ -75,10 +76,10 @@ public class DbImporterMojoConfigurationTest extends 
AbstractMojoTestCase {
 
         FiltersConfig filters = 
dbImportConfiguration.getDbLoaderConfig().getFiltersConfig();
 
-        TreeSet<IncludeTableFilter> includes = new TreeSet<>();
+        List<IncludeTableFilter> includes = new ArrayList<>();
         includes.add(new IncludeTableFilter(null, new 
PatternFilter().exclude("^ETL_.*")));
 
-        TreeSet<Pattern> excludes = new 
TreeSet<>(PatternFilter.PATTERN_COMPARATOR);
+        List<Pattern> excludes = new ArrayList<>();
         excludes.add(PatternFilter.pattern("^ETL_.*"));
 
         assertEquals(filters.tableFilter(null, "NHL_STATS"),
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/DefaultActionManager.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/DefaultActionManager.java
index 1aec38f2f..24dd89518 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/DefaultActionManager.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/DefaultActionManager.java
@@ -31,6 +31,7 @@ import 
org.apache.cayenne.modeler.action.dbimport.AddIncludeProcedureAction;
 import org.apache.cayenne.modeler.action.dbimport.AddIncludeTableAction;
 import org.apache.cayenne.modeler.action.dbimport.AddSchemaAction;
 import org.apache.cayenne.modeler.action.dbimport.DeleteNodeAction;
+import org.apache.cayenne.modeler.action.dbimport.DragAndDropNodeAction;
 import org.apache.cayenne.modeler.action.dbimport.EditNodeAction;
 import org.apache.cayenne.modeler.action.dbimport.MoveImportNodeAction;
 import org.apache.cayenne.modeler.action.dbimport.MoveInvertNodeAction;
@@ -124,7 +125,9 @@ public class DefaultActionManager implements ActionManager {
         registerAction(new EditNodeAction(application)).setAlwaysOn(true);
         registerAction(new DeleteNodeAction(application)).setAlwaysOn(true);
         registerAction(new 
MoveImportNodeAction(application)).setAlwaysOn(true);
+        registerAction(new 
DragAndDropNodeAction(application)).setAlwaysOn(true);
         registerAction(new LoadDbSchemaAction(application)).setAlwaysOn(true);
+        registerAction(new SortNodesAction(application)).setAlwaysOn(true);
         registerAction(new 
MoveInvertNodeAction(application)).setAlwaysOn(true);
         registerAction(new AboutAction(application)).setAlwaysOn(true);
         registerAction(new DocumentationAction(application)).setAlwaysOn(true);
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/SortNodesAction.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/SortNodesAction.java
new file mode 100644
index 000000000..fd7f856ff
--- /dev/null
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/SortNodesAction.java
@@ -0,0 +1,59 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.modeler.action;
+
+import org.apache.cayenne.dbsync.reverse.dbimport.ReverseEngineering;
+import org.apache.cayenne.modeler.Application;
+import org.apache.cayenne.modeler.action.dbimport.TreeManipulationAction;
+import org.apache.cayenne.modeler.dialog.db.load.DbImportTreeNode;
+import org.apache.cayenne.modeler.editor.dbimport.DbImportSorter;
+
+import java.awt.event.ActionEvent;
+import java.util.List;
+
+/**
+ * @since 5.0
+ */
+public class SortNodesAction extends TreeManipulationAction {
+
+    private static final String ACTION_NAME = "Sort";
+    private static final String ICON_NAME = "icon-dbi-sort.png";
+
+    public SortNodesAction(Application application) {
+        super(ACTION_NAME, application);
+    }
+
+    public String getIconName() {
+        return ICON_NAME;
+    }
+
+    @Override
+    public void performAction(ActionEvent e) {
+        tree.stopEditing();
+        ReverseEngineering reverseEngineeringOldCopy = new 
ReverseEngineering(tree.getReverseEngineering());
+        List<DbImportTreeNode> treeExpandList = tree.getTreeExpandList();
+        DbImportSorter.sortSubtree(tree.getRootNode(), 
DbImportSorter.NODE_COMPARATOR_BY_TYPE_BY_NAME);
+        putReverseEngineeringToUndoManager(reverseEngineeringOldCopy);
+        getProjectController().setDirty(true);
+        tree.reloadModelKeepingExpanded();
+        tree.expandTree(treeExpandList);
+    }
+
+}
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/dbimport/DeleteNodeAction.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/dbimport/DeleteNodeAction.java
index 70f51a47d..4093d9553 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/dbimport/DeleteNodeAction.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/dbimport/DeleteNodeAction.java
@@ -38,7 +38,7 @@ import 
org.apache.cayenne.modeler.editor.dbimport.DraggableTreePanel;
 
 import javax.swing.tree.TreePath;
 import java.awt.event.ActionEvent;
-import java.util.ArrayList;
+import java.util.List;
 
 /**
  * @since 4.1
@@ -117,7 +117,7 @@ public class DeleteNodeAction extends 
TreeManipulationAction {
         DbImportModel model = (DbImportModel) tree.getModel();
         model.removeNodeFromParent(selectedElement);
         getProjectController().setDirty(true);
-        model.reload(parentElement);
+        tree.reloadModelKeepingExpanded(parentElement);
     }
 
     @Override
@@ -152,7 +152,7 @@ public class DeleteNodeAction extends 
TreeManipulationAction {
             }
             if (paths.length > 1) {
                 getProjectController().setDirty(true);
-                ArrayList<DbImportTreeNode> expandList = 
tree.getTreeExpandList();
+                List<DbImportTreeNode> expandList = tree.getTreeExpandList();
                 
tree.translateReverseEngineeringToTree(tree.getReverseEngineering(), false);
                 tree.expandTree(expandList);
             } else {
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/dbimport/DragAndDropNodeAction.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/dbimport/DragAndDropNodeAction.java
new file mode 100644
index 000000000..8779723eb
--- /dev/null
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/dbimport/DragAndDropNodeAction.java
@@ -0,0 +1,157 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.modeler.action.dbimport;
+
+import org.apache.cayenne.dbsync.reverse.dbimport.ReverseEngineering;
+import org.apache.cayenne.modeler.Application;
+import org.apache.cayenne.modeler.dialog.db.load.DbImportTreeNode;
+import org.apache.cayenne.modeler.editor.dbimport.DbImportModel;
+import org.apache.cayenne.modeler.editor.dbimport.DbImportSorter;
+
+import javax.swing.JOptionPane;
+import javax.swing.JTree;
+import javax.swing.tree.TreePath;
+import java.awt.event.ActionEvent;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @since 5.0
+ */
+public class DragAndDropNodeAction extends TreeManipulationAction {
+
+    private static final String ACTION_NAME = "DragAndDrop";
+    private DbImportTreeNode[] nodes;
+    private DbImportTreeNode dropLocationParentNode;
+    private DbImportTreeNode sourceParentNode;
+    private JTree.DropLocation dropLocation;
+
+    public DragAndDropNodeAction(Application application) {
+        super(ACTION_NAME, application);
+    }
+
+    @Override
+    public void performAction(ActionEvent e) {
+        if (dropLocationDuplicateFound()) {
+            return;
+        }
+        DbImportModel model = (DbImportModel) tree.getModel();
+        ReverseEngineering reverseEngineeringOldCopy = new 
ReverseEngineering(tree.getReverseEngineering());
+        List<DbImportTreeNode> nodesToExpand = Arrays.stream(nodes)
+                .filter(node -> tree.isExpanded(new TreePath(node.getPath())))
+                .collect(Collectors.toList());
+
+        for (DbImportTreeNode node : nodes) {
+            if (checkDropPossibility(node)) {
+                int index = calculateDropIndex();
+                model.removeNodeFromParent(node);
+                model.insertNodeInto(node, dropLocationParentNode, index);
+            }
+        }
+        getProjectController().setDirty(true);
+        DbImportSorter.syncUserObjectItems(dropLocationParentNode);
+        DbImportSorter.syncUserObjectItems(sourceParentNode);
+        putReverseEngineeringToUndoManager(reverseEngineeringOldCopy);
+        tree.reloadModelKeepingExpanded(dropLocationParentNode);
+        tree.expandTree(nodesToExpand);
+    }
+
+    private boolean dropLocationDuplicateFound() {
+        for (DbImportTreeNode node : nodes) {
+            if (dropLocationParentNode.isNodeChild(node)) {
+                // we are fine about this
+                continue;
+            }
+            int duplicateIndex = 
dropLocationParentNode.getChildNodes().indexOf(node);
+            if (duplicateIndex >= 0) {
+                JOptionPane.showMessageDialog(
+                        Application.getFrame(),
+                        dropLocationParentNode.getSimpleNodeName() + " already 
contains " + node.getSimpleNodeName(),
+                        "Error moving",
+                        JOptionPane.ERROR_MESSAGE);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private int calculateDropIndex() {
+        int index = dropLocation.getChildIndex();
+        //node moving inside a one node
+        if (sourceParentNode == dropLocationParentNode) {
+            int childCount = dropLocationParentNode.getChildCount();
+            int childIndex = dropLocation.getChildIndex();
+            if (childIndex == childCount) {
+                index = childCount - 1;
+            }
+        }
+        //If target node is collapsed
+        if (index == -1 && sourceParentNode != dropLocationParentNode) {
+            index = dropLocationParentNode.getChildCount();
+        }
+
+        //If the target node is an expanded parent node, we place the node in 
the first position
+        if (index == -1 && sourceParentNode == dropLocationParentNode) {
+            index = 0;
+        }
+        return index;
+    }
+
+    private boolean checkDropPossibility(DbImportTreeNode node) {
+        // Don't allow a node to be dropped onto itself
+        if (node == dropLocationParentNode) {
+            return false;
+        }
+        // Don't allow a node to be dropped onto one of its descendants
+        for (DbImportTreeNode childNode : node.getChildNodes()) {
+            if (isNodeAncestor(childNode, dropLocationParentNode)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean isNodeAncestor(DbImportTreeNode node1, DbImportTreeNode 
node2) {
+        if (node2 == null) {
+            return false;
+        }
+        if (node2.getParent() == node1) {
+            return true;
+        }
+        return isNodeAncestor(node1, node2.getParent());
+    }
+
+    public void setNodes(DbImportTreeNode[] nodes) {
+        this.nodes = nodes;
+    }
+
+    public void setDropLocationParentNode(DbImportTreeNode 
dropLocationParentNode) {
+        this.dropLocationParentNode = dropLocationParentNode;
+    }
+
+    public void setSourceParentNode(DbImportTreeNode sourceParentNode) {
+        this.sourceParentNode = sourceParentNode;
+    }
+
+    public void setDropLocation(JTree.DropLocation dropLocation) {
+        this.dropLocation = dropLocation;
+    }
+}
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/dbimport/EditNodeAction.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/dbimport/EditNodeAction.java
index 1e1864a4b..a1e9ffc3a 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/dbimport/EditNodeAction.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/dbimport/EditNodeAction.java
@@ -79,8 +79,8 @@ public class EditNodeAction extends TreeManipulationAction {
                     
putReverseEngineeringToUndoManager(reverseEngineeringOldCopy);
                 }
             }
-            DbImportSorter.sortSingleNode(selectedElement.getParent());
-            tree.reloadModel();
+            
DbImportSorter.sortSingleNode(selectedElement.getParent(),DbImportSorter.NODE_COMPARATOR_BY_TYPE);
+            tree.reloadModelKeepingExpanded();
             tree.setSelectionPath(new TreePath(selectedElement.getPath()));
             selectedElement = null;
         }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/dbimport/MoveImportNodeAction.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/dbimport/MoveImportNodeAction.java
index 455082808..4c0474051 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/dbimport/MoveImportNodeAction.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/dbimport/MoveImportNodeAction.java
@@ -39,8 +39,8 @@ import org.apache.cayenne.modeler.util.CayenneAction;
 
 import javax.swing.tree.TreePath;
 import java.awt.event.ActionEvent;
-import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.regex.Matcher;
 
@@ -187,7 +187,7 @@ public class MoveImportNodeAction extends CayenneAction {
                 }
                 if ((paths.length > 1) && (targetTree.getSelectionPath() != 
null)) {
                     getProjectController().setDirty(true);
-                    ArrayList<DbImportTreeNode> expandList = 
targetTree.getTreeExpandList();
+                    List<DbImportTreeNode> expandList = 
targetTree.getTreeExpandList();
                     
targetTree.translateReverseEngineeringToTree(targetTree.getReverseEngineering(),
 false);
                     targetTree.expandTree(expandList);
                 }
@@ -199,8 +199,8 @@ public class MoveImportNodeAction extends CayenneAction {
                     
getProjectController().getApplication().getUndoManager().addEdit(undoableEdit);
                 }
                 if (foundNode != null) {
-                    DbImportSorter.sortSubtree((DbImportTreeNode) 
foundNode.getRoot());
-                    targetTree.reloadModel();
+                    DbImportSorter.sortSubtree((DbImportTreeNode) 
foundNode.getRoot(),DbImportSorter.NODE_COMPARATOR_BY_TYPE);
+                    targetTree.reloadModelKeepingExpanded();
                     targetTree.setSelectionPath(new 
TreePath(foundNode.getLastChild().getPath()));
                 }
             } finally {
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/dbimport/TreeManipulationAction.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/dbimport/TreeManipulationAction.java
index fbbc47879..e3cbd9462 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/dbimport/TreeManipulationAction.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/dbimport/TreeManipulationAction.java
@@ -190,7 +190,7 @@ public abstract class TreeManipulationAction extends 
CayenneAction {
         if (!updateSelected) {
             savedPath = new TreePath(parentElement.getPath());
         }
-        model.reload(updateSelected ? selectedElement : parentElement);
+        tree.reloadModelKeepingExpanded(updateSelected ? selectedElement : 
parentElement);
         if ((savedPath != null) && (parentElement.getUserObject().getClass() 
!= ReverseEngineering.class)) {
             tree.setSelectionPath(savedPath);
         }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/DbImportTreeNode.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/DbImportTreeNode.java
index ada7428d0..332953b9f 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/DbImportTreeNode.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/DbImportTreeNode.java
@@ -36,6 +36,7 @@ import javax.swing.tree.DefaultMutableTreeNode;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.stream.Collectors;
 
 
@@ -50,64 +51,72 @@ public class DbImportTreeNode extends 
DefaultMutableTreeNode {
         this(null);
     }
 
+    public DbImportTreeNode(Object userObject) {
+        this(userObject, true);
+    }
+
     private DbImportTreeNode(Object userObject, boolean allowsChildren) {
         super();
         this.userObject = userObject;
         this.allowsChildren = allowsChildren;
         parent = null;
+
+        if (userObject != null && (isCatalog() || isSchema() || 
isIncludeTable())) {
+            add(new ExpandableEnforcerNode());
+        }
     }
 
     public boolean isIncludeTable() {
-        return (getUserObject().getClass() == IncludeTable.class);
+        return (getUserObjectClass() == IncludeTable.class);
     }
 
     public boolean isExcludeTable() {
-        return (getUserObject().getClass() == ExcludeTable.class);
+        return (getUserObjectClass() == ExcludeTable.class);
     }
 
     public boolean isIncludeColumn() {
-        return (getUserObject().getClass() == IncludeColumn.class);
+        return (getUserObjectClass() == IncludeColumn.class);
     }
 
     public boolean isExcludeColumn() {
-        return (getUserObject().getClass() == ExcludeColumn.class);
+        return (getUserObjectClass() == ExcludeColumn.class);
     }
 
     public boolean isExcludeProcedure() {
-        return (getUserObject().getClass() == ExcludeProcedure.class);
+        return (getUserObjectClass() == ExcludeProcedure.class);
     }
 
     public boolean isIncludeProcedure() {
-        return (getUserObject().getClass() == IncludeProcedure.class);
+        return (getUserObjectClass() == IncludeProcedure.class);
     }
 
     public boolean isLabel() {
-        return (getUserObject().getClass() == String.class);
+        return (getUserObjectClass() == String.class);
     }
 
     public boolean isSchema() {
-        return (getUserObject().getClass() == Schema.class);
+        return (getUserObjectClass() == Schema.class);
     }
 
     public boolean isCatalog() {
-        return (getUserObject().getClass() == Catalog.class);
+        return (getUserObjectClass() == Catalog.class);
     }
 
     public boolean isReverseEngineering() {
-        return (getUserObject().getClass() == ReverseEngineering.class);
+        return (getUserObjectClass() == ReverseEngineering.class);
     }
 
-    public DbImportTreeNode(Object userObject) {
-        this(userObject, true);
+    public Class<?> getUserObjectClass() {
+        return getUserObject() != null ? getUserObject().getClass() : null;
     }
 
     // Compare parents chain
     public boolean parentsIsEqual(DbImportTreeNode reverseEngineeringNode) {
         ArrayList<DbImportTreeNode> reverseEngineeringNodeParents;
         if (reverseEngineeringNode == null) {
-                reverseEngineeringNodeParents = new ArrayList<>();
+            reverseEngineeringNodeParents = new ArrayList<>();
         } else {
-             reverseEngineeringNodeParents = 
reverseEngineeringNode.getParents();
+            reverseEngineeringNodeParents = 
reverseEngineeringNode.getParents();
         }
         ArrayList<DbImportTreeNode> dbNodeParents = getParents();
         for (DbImportTreeNode node : reverseEngineeringNodeParents) {
@@ -140,7 +149,7 @@ public class DbImportTreeNode extends 
DefaultMutableTreeNode {
 
     @Override
     public DbImportTreeNode getParent() {
-        return (DbImportTreeNode)super.getParent();
+        return (DbImportTreeNode) super.getParent();
     }
 
     protected String getFormattedName(String className, String nodeName) {
@@ -194,13 +203,13 @@ public class DbImportTreeNode extends 
DefaultMutableTreeNode {
             return false;
         }
         DbImportTreeNode objNode = (DbImportTreeNode) obj;
-        if (!objNode.getSimpleNodeName().equals(this.getSimpleNodeName())) {
-            return false;
-        }
-        if (objNode.getUserObject().getClass() != 
this.getUserObject().getClass()) {
-            return false;
-        }
-        return true;
+        return Objects.equals(getSimpleNodeName(), objNode.getSimpleNodeName())
+                && getUserObjectClass() == objNode.getUserObjectClass();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getSimpleNodeName(), getUserObjectClass());
     }
 
     public boolean isLoaded() {
@@ -250,4 +259,11 @@ public class DbImportTreeNode extends 
DefaultMutableTreeNode {
     public DbImportTreeNode getLastChild() {
         return (DbImportTreeNode) super.getLastChild();
     }
+
+    public static class ExpandableEnforcerNode extends DbImportTreeNode {
+
+        public ExpandableEnforcerNode() {
+            super("", false);
+        }
+    }
 }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/TransferableNode.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/TransferableNode.java
index a7d8af6b3..7a8eaa8f0 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/TransferableNode.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/TransferableNode.java
@@ -20,11 +20,8 @@
 package org.apache.cayenne.modeler.dialog.db.load;
 
 import org.apache.cayenne.dbsync.reverse.dbimport.Catalog;
-import org.apache.cayenne.dbsync.reverse.dbimport.FilterContainer;
-import org.apache.cayenne.dbsync.reverse.dbimport.IncludeProcedure;
 import org.apache.cayenne.dbsync.reverse.dbimport.IncludeTable;
 import org.apache.cayenne.dbsync.reverse.dbimport.PatternParam;
-import org.apache.cayenne.dbsync.reverse.dbimport.ReverseEngineering;
 import org.apache.cayenne.dbsync.reverse.dbimport.Schema;
 
 import java.awt.datatransfer.DataFlavor;
@@ -45,7 +42,7 @@ public class TransferableNode extends DbImportTreeNode 
implements Transferable {
                                                                     
includeTableFlavor, patternParamFlavor };
 
     public TransferableNode(Object userObject) {
-        this.userObject = userObject;
+        super(userObject);
     }
 
     @Override
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportSorter.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportSorter.java
index b44bc6246..2b585d6d4 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportSorter.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportSorter.java
@@ -36,28 +36,32 @@ import java.util.List;
  * @since 5.0
  */
 public class DbImportSorter {
-    private static final Comparator<DbImportTreeNode> NODE_COMPARATOR = 
Comparator
+    public static final Comparator<DbImportTreeNode> NODE_COMPARATOR_BY_TYPE = 
Comparator
+            .comparing(DbImportTreeNode::getNodeType);
+
+
+    public static final Comparator<DbImportTreeNode> 
NODE_COMPARATOR_BY_TYPE_BY_NAME = Comparator
             .comparing(DbImportTreeNode::getNodeType)
             .thenComparing(DbImportTreeNode::getSimpleNodeName);
 
-    public static void sortSingleNode(DbImportTreeNode node) {
-        sortNodeItems(node);
+    public static void sortSingleNode(DbImportTreeNode node, 
Comparator<DbImportTreeNode> comparator) {
+        sortNodeItems(node, comparator);
         syncUserObjectItems(node);
     }
 
-    public static void sortSubtree(DbImportTreeNode root) {
-        sortSingleNode(root);
-        root.getChildNodes().forEach(DbImportSorter::sortSubtree);
+    public static void sortSubtree(DbImportTreeNode 
root,Comparator<DbImportTreeNode> comparator) {
+        sortSingleNode(root, comparator);
+        root.getChildNodes().forEach(r -> sortSubtree(r, comparator));
     }
 
-    private static void sortNodeItems(DbImportTreeNode node) {
+    private static void sortNodeItems(DbImportTreeNode node, 
Comparator<DbImportTreeNode> comparator) {
         List<DbImportTreeNode> childNodes = node.getChildNodes();
         node.removeAllChildren();
-        childNodes.sort(NODE_COMPARATOR);
+        childNodes.sort(comparator);
         childNodes.forEach(node::add);
     }
 
-    private static void syncUserObjectItems(DbImportTreeNode parentNode) {
+    public static void syncUserObjectItems(DbImportTreeNode parentNode) {
 
         Object userObject = parentNode.getUserObject();
 
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportTree.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportTree.java
index 93afaedb8..b2262e071 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportTree.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportTree.java
@@ -35,11 +35,13 @@ import 
org.apache.cayenne.modeler.dialog.db.load.TransferableNode;
 import javax.swing.JTree;
 import javax.swing.event.TreeExpansionEvent;
 import javax.swing.event.TreeExpansionListener;
+import javax.swing.plaf.basic.BasicTreeUI;
 import javax.swing.tree.TreeNode;
 import javax.swing.tree.TreePath;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 import java.util.function.BiFunction;
 
 
@@ -53,6 +55,8 @@ public class DbImportTree extends JTree {
 
     public DbImportTree(TreeNode node) {
         super(node);
+        setRowHeight(0);
+        setUI(new TreeUI());
         createTreeExpandListener();
     }
 
@@ -71,7 +75,7 @@ public class DbImportTree extends JTree {
         printParams(reverseEngineering.getExcludeColumns(), root);
         printParams(reverseEngineering.getIncludeProcedures(), root);
         printParams(reverseEngineering.getExcludeProcedures(), root);
-        DbImportSorter.sortSubtree(root);
+        DbImportSorter.sortSubtree(root, 
DbImportSorter.NODE_COMPARATOR_BY_TYPE);
         model.reload();
     }
 
@@ -212,7 +216,7 @@ public class DbImportTree extends JTree {
     }
 
     // Create list of expanded elements
-    private ArrayList<DbImportTreeNode> createTreeExpandList(DbImportTreeNode 
rootNode, ArrayList<DbImportTreeNode> resultList) {
+    private List<DbImportTreeNode> createTreeExpandList(DbImportTreeNode 
rootNode, List<DbImportTreeNode> resultList) {
         for (int i = 0; i < rootNode.getChildCount(); i++) {
             DbImportTreeNode childNode = (DbImportTreeNode) 
rootNode.getChildAt(i);
             TreePath childPath = new TreePath(childNode.getPath());
@@ -226,12 +230,23 @@ public class DbImportTree extends JTree {
         return resultList;
     }
 
-    public ArrayList<DbImportTreeNode> getTreeExpandList() {
+    public void reloadModelKeepingExpanded(DbImportTreeNode node) {
+        DbImportModel model = (DbImportModel) getModel();
+        List<DbImportTreeNode> nodesToExpand = getTreeExpandList();
+        model.reload(node);
+        expandTree(nodesToExpand);
+    }
+
+    public void reloadModelKeepingExpanded() {
+        reloadModelKeepingExpanded(getRootNode());
+    }
+
+    public List<DbImportTreeNode> getTreeExpandList() {
         ArrayList<DbImportTreeNode> resultList = new ArrayList<>();
         return createTreeExpandList(getRootNode(), resultList);
     }
 
-    private void expandBeginningWithNode(DbImportTreeNode rootNode, 
ArrayList<DbImportTreeNode> list) {
+    private void expandBeginningWithNode(DbImportTreeNode rootNode, 
List<DbImportTreeNode> list) {
         for (int i = 0; i < rootNode.getChildCount(); i++) {
             DbImportTreeNode childNode = (DbImportTreeNode) 
rootNode.getChildAt(i);
             list.forEach((element) -> {
@@ -245,7 +260,7 @@ public class DbImportTree extends JTree {
         }
     }
 
-    public void expandTree(ArrayList<DbImportTreeNode> expandIndexesList) {
+    public void expandTree(List<DbImportTreeNode> expandIndexesList) {
         expandBeginningWithNode(getRootNode(), expandIndexesList);
     }
 
@@ -258,15 +273,10 @@ public class DbImportTree extends JTree {
         }
     }
 
-    public void reloadModel(){
-        DbImportModel model = (DbImportModel)this.getModel();
-        model.reload();
-    }
-
     private void printIncludeTables(Collection<IncludeTable> collection, 
DbImportTreeNode parent) {
         for (IncludeTable includeTable : collection) {
             DbImportTreeNode node = !isTransferable ? new 
DbImportTreeNode(includeTable) : new TransferableNode(includeTable);
-            if (!node.getSimpleNodeName().equals("")) {
+            if (!node.getSimpleNodeName().isEmpty()) {
 
                 if (isTransferable && 
includeTable.getIncludeColumns().isEmpty() && 
includeTable.getExcludeColumns().isEmpty()) {
                     printParams(Collections.singletonList(new 
IncludeColumn("Loading...")), node);
@@ -370,4 +380,18 @@ public class DbImportTree extends JTree {
     public boolean isTransferable() {
         return isTransferable;
     }
+
+    static class TreeUI extends BasicTreeUI {
+
+        @Override
+        protected boolean shouldPaintExpandControl(TreePath path, int row, 
boolean isExpanded,
+                                                   boolean hasBeenExpanded, 
boolean isLeaf) {
+            DbImportTreeNode node = (DbImportTreeNode) 
path.getLastPathComponent();
+            int childCount = node.getChildCount();
+            boolean onlyEnforcerChild = childCount == 1
+                    && node.getFirstChild().getClass() == 
DbImportTreeNode.ExpandableEnforcerNode.class;
+            return super.shouldPaintExpandControl(path, row, isExpanded, 
hasBeenExpanded, isLeaf)
+                    && (childCount > 1 || !onlyEnforcerChild);
+        }
+    }
 }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportTreeCellEditor.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportTreeCellEditor.java
index 1789f25cb..e4e354c54 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportTreeCellEditor.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportTreeCellEditor.java
@@ -26,6 +26,7 @@ import 
org.apache.cayenne.modeler.dialog.db.load.DbImportTreeNode;
 import org.apache.cayenne.util.Util;
 
 import javax.swing.JTree;
+import javax.swing.UIManager;
 import javax.swing.event.CellEditorListener;
 import javax.swing.event.ChangeEvent;
 import javax.swing.tree.DefaultTreeCellEditor;
@@ -44,6 +45,7 @@ public class DbImportTreeCellEditor extends 
DefaultTreeCellEditor {
 
     public DbImportTreeCellEditor(JTree tree, DefaultTreeCellRenderer 
renderer) {
         super(tree, renderer);
+        setFont(UIManager.getFont("Tree.font"));
         this.addCellEditorListener(new CellEditorListener() {
             @Override
             public void editingStopped(ChangeEvent e) {
@@ -73,8 +75,7 @@ public class DbImportTreeCellEditor extends 
DefaultTreeCellEditor {
         if (value instanceof DbImportTreeNode) {
             value = ((DbImportTreeNode) value).getSimpleNodeName();
         }
-        return super.getTreeCellEditorComponent(tree, value, isSelected, 
expanded,
-                leaf, row);
+        return super.getTreeCellEditorComponent(tree, value, isSelected, 
expanded, leaf, row);
     }
 
     @Override
@@ -122,7 +123,7 @@ public class DbImportTreeCellEditor extends 
DefaultTreeCellEditor {
         }
         if (tree.getSelectionPath() != null) {
             DbImportTreeNode selectedNode = (DbImportTreeNode) 
tree.getSelectionPath().getLastPathComponent();
-            ((DbImportModel) tree.getModel()).reload(selectedNode);
+            ((DbImportTree) tree).reloadModelKeepingExpanded(selectedNode);
         }
     }
 
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportTreeCellRenderer.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportTreeCellRenderer.java
index d2a1ce14e..4e5e5ed47 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportTreeCellRenderer.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportTreeCellRenderer.java
@@ -30,10 +30,13 @@ import org.apache.cayenne.dbsync.reverse.dbimport.Schema;
 import org.apache.cayenne.modeler.dialog.db.load.DbImportTreeNode;
 import org.apache.cayenne.modeler.util.ModelerUtil;
 
+import javax.swing.Icon;
 import javax.swing.ImageIcon;
+import javax.swing.JLabel;
 import javax.swing.JTree;
 import javax.swing.tree.DefaultTreeCellRenderer;
 import java.awt.Component;
+import java.awt.Dimension;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -43,16 +46,15 @@ import java.util.Map;
 public class DbImportTreeCellRenderer extends DefaultTreeCellRenderer {
 
     protected DbImportTreeNode node;
-    private Map<Class, String> icons;
-    private Map<Class, String> transferableTreeIcons;
+    private Map<Class<?>, String> icons;
+    private Map<Class<?>, String> transferableTreeIcons;
 
     public DbImportTreeCellRenderer() {
-        super();
         initIcons();
-        initTrasferableTreeIcons();
+        initTransferableTreeIcons();
     }
 
-    private void initTrasferableTreeIcons() {
+    private void initTransferableTreeIcons() {
         transferableTreeIcons = new HashMap<>();
         transferableTreeIcons.put(Catalog.class, "icon-dbi-catalog.png");
         transferableTreeIcons.put(Schema.class, "icon-dbi-schema.png");
@@ -73,7 +75,7 @@ public class DbImportTreeCellRenderer extends 
DefaultTreeCellRenderer {
         icons.put(ExcludeProcedure.class, "icon-dbi-excludeProcedure.png");
     }
 
-    private ImageIcon getIconByNodeType(Class nodeClass, boolean 
isTransferable) {
+    private ImageIcon getIconByNodeType(Class<?> nodeClass, boolean 
isTransferable) {
         String iconName = !isTransferable ? icons.get(nodeClass) : 
transferableTreeIcons.get(nodeClass);
         if (iconName == null) {
             return null;
@@ -91,6 +93,44 @@ public class DbImportTreeCellRenderer extends 
DefaultTreeCellRenderer {
         super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, 
row, hasFocus);
         node = (DbImportTreeNode) value;
         setIcon(getIconByNodeType(node.getUserObject().getClass(), 
((DbImportTree) tree).isTransferable()));
-        return this;
+        return value instanceof DbImportTreeNode.ExpandableEnforcerNode
+                ? ExpandableEnforcer.getInstance()
+                : this;
+    }
+
+    @Override
+    public Icon getLeafIcon() {
+        return null;
+    }
+
+    @Override
+    public Icon getOpenIcon() {
+        return null;
+    }
+
+    @Override
+    public Icon getClosedIcon() {
+        return null;
+    }
+
+    private static class ExpandableEnforcer extends JLabel {
+
+        private static ExpandableEnforcer instance;
+
+        public ExpandableEnforcer() {
+            setPreferredSize(new Dimension(0, 0));
+        }
+
+        public static ExpandableEnforcer getInstance() {
+            if (instance == null) {
+                instance = new ExpandableEnforcer();
+            }
+            return instance;
+        }
+
+        @Override
+        public Dimension getPreferredSize() {
+            return super.getPreferredSize();
+        }
     }
 }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DraggableTreePanel.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DraggableTreePanel.java
index 320ac406b..a24b3ad3a 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DraggableTreePanel.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DraggableTreePanel.java
@@ -38,6 +38,7 @@ import 
org.apache.cayenne.modeler.action.dbimport.AddIncludeColumnAction;
 import org.apache.cayenne.modeler.action.dbimport.AddIncludeProcedureAction;
 import org.apache.cayenne.modeler.action.dbimport.AddIncludeTableAction;
 import org.apache.cayenne.modeler.action.dbimport.AddSchemaAction;
+import org.apache.cayenne.modeler.action.dbimport.DragAndDropNodeAction;
 import org.apache.cayenne.modeler.action.dbimport.MoveImportNodeAction;
 import org.apache.cayenne.modeler.action.dbimport.MoveInvertNodeAction;
 import org.apache.cayenne.modeler.action.dbimport.TreeManipulationAction;
@@ -54,7 +55,6 @@ import javax.swing.JTree;
 import javax.swing.TransferHandler;
 import javax.swing.event.TreeSelectionEvent;
 import javax.swing.event.TreeSelectionListener;
-import javax.swing.tree.DefaultTreeCellRenderer;
 import javax.swing.tree.TreePath;
 import java.awt.datatransfer.DataFlavor;
 import java.awt.datatransfer.Transferable;
@@ -93,6 +93,7 @@ public class DraggableTreePanel extends JScrollPane {
 
     private CayenneAction.CayenneToolbarButton moveButton;
     private CayenneAction.CayenneToolbarButton moveInvertButton;
+    private ImportSourceTree importSourceTree;
 
     public DraggableTreePanel(ProjectController projectController, 
DbImportTree sourceTree, DbImportTree targetTree) {
         super(sourceTree);
@@ -143,6 +144,7 @@ public class DraggableTreePanel extends JScrollPane {
         targetTree.setTransferHandler(new TargetTreeTransferHandler());
         targetTree.addTreeSelectionListener(new TargetTreeSelectionListener());
         targetTree.setDragEnabled(true);
+        targetTree.setDropMode(DropMode.INSERT);
     }
 
     private boolean canBeInverted() {
@@ -174,12 +176,6 @@ public class DraggableTreePanel extends JScrollPane {
         moveInvertButton = (CayenneAction.CayenneToolbarButton) 
actionInv.buildButton();
         moveInvertButton.setShowingText(true);
         moveInvertButton.setText(MOVE_INV_BUTTON_LABEL);
-
-
-        DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) 
sourceTree.getCellRenderer();
-        renderer.setLeafIcon(null);
-        renderer.setClosedIcon(null);
-        renderer.setOpenIcon(null);
     }
 
     private void initLevels() {
@@ -226,7 +222,7 @@ public class DraggableTreePanel extends JScrollPane {
 
         if (selectedElement.isIncludeColumn() || 
selectedElement.isExcludeColumn()) {
             DbImportTreeNode node = 
targetTree.findNode(targetTree.getRootNode(), selectedElement.getParent(), 0);
-            if(node != null && node.isExcludeTable()) {
+            if (node != null && node.isExcludeTable()) {
                 return false;
             }
         }
@@ -273,7 +269,7 @@ public class DraggableTreePanel extends JScrollPane {
         return sourceTree;
     }
 
-    private static class SourceTreeTransferHandler extends TransferHandler {
+    private class SourceTreeTransferHandler extends TransferHandler {
 
         @Override
         public int getSourceActions(JComponent c) {
@@ -282,6 +278,7 @@ public class DraggableTreePanel extends JScrollPane {
 
         @Override
         public Transferable createTransferable(JComponent c) {
+            importSourceTree = ImportSourceTree.SOURCE_TREE;
             JTree tree = (JTree) c;
             TreePath[] paths = tree.getSelectionPaths();
             int pathLength = paths == null ? 0 : paths.length;
@@ -356,14 +353,59 @@ public class DraggableTreePanel extends JScrollPane {
     }
 
     private class TargetTreeTransferHandler extends TransferHandler {
+        private DbImportTreeNode sourceParentNode;
+
+        @Override
+        protected Transferable createTransferable(JComponent c) {
+            JTree tree = (JTree) c;
+            importSourceTree = ImportSourceTree.TARGET_TREE;
+            TreePath[] paths = tree.getSelectionPaths();
+            DbImportTreeNode lastSelectedNode = (DbImportTreeNode) 
tree.getLastSelectedPathComponent();
+            sourceParentNode = lastSelectedNode.getParent();
+            if (paths != null && paths.length > 0) {
+                DbImportTreeNode[] nodes = new DbImportTreeNode[paths.length];
+                for (int i = 0; i < paths.length; i++) {
+                    nodes[i] = (DbImportTreeNode) 
paths[i].getLastPathComponent();
+                }
+                return new Transferable() {
+                    @Override
+                    public DataFlavor[] getTransferDataFlavors() {
+                        return TransferableNode.flavors;
+                    }
+
+                    @Override
+                    public boolean isDataFlavorSupported(DataFlavor flavor) {
+                        return true;
+                    }
+
+                    @Override
+                    public Object getTransferData(DataFlavor flavor) {
+                        return nodes;
+                    }
+                };
+            }
+            return null;
+        }
 
         @Override
         public int getSourceActions(JComponent c) {
-            return COPY_OR_MOVE;
+            return TransferHandler.MOVE;
         }
 
         @Override
         public boolean canImport(TransferSupport support) {
+            JTree.DropLocation dropLocation = (JTree.DropLocation) 
support.getDropLocation();
+            DbImportTreeNode dropLocationParentNode = (DbImportTreeNode) 
dropLocation.getPath().getLastPathComponent();
+
+            List<Class<?>> allowedItemsList = 
insertableLevels.get(dropLocationParentNode.getUserObject().getClass());
+            DbImportTreeNode[] nodes = getNodesFromSupport(support);
+            if (nodes != null && allowedItemsList != null) {
+                for (DbImportTreeNode node : nodes) {
+                    if 
(!allowedItemsList.contains(node.getUserObject().getClass())) {
+                        return false;
+                    }
+                }
+            }
             return support.isDrop();
         }
 
@@ -372,19 +414,21 @@ public class DraggableTreePanel extends JScrollPane {
             if (!canImport(support)) {
                 return false;
             }
-            if (!canBeMoved()) {
-                return false;
+            if (importSourceTree == ImportSourceTree.TARGET_TREE) {
+                return importDataFromTargetTree(support);
             }
-            Transferable transferable = support.getTransferable();
-            DbImportTreeNode[] transferData = null;
-            try {
-                for (DataFlavor dataFlavor : 
transferable.getTransferDataFlavors()) {
-                    transferData = (DbImportTreeNode[]) 
transferable.getTransferData(dataFlavor);
-                }
-            } catch (IOException | UnsupportedFlavorException e) {
+            if (importSourceTree == ImportSourceTree.SOURCE_TREE) {
+                return importDataFromSourceTree(support);
+            }
+            return false;
+        }
+
+        private boolean importDataFromSourceTree(TransferSupport support) {
+            if (!canBeMoved()) {
                 return false;
             }
-            if (transferData != null) {
+            DbImportTreeNode[] nodes = getNodesFromSupport(support);
+            if (nodes != null) {
                 MoveImportNodeAction action = 
projectController.getApplication().getActionManager()
                         .getAction(MoveImportNodeAction.class);
                 action.setSourceTree(sourceTree);
@@ -395,6 +439,38 @@ public class DraggableTreePanel extends JScrollPane {
             }
             return false;
         }
+
+        private boolean importDataFromTargetTree(TransferSupport support) {
+            JTree.DropLocation dropLocation = (JTree.DropLocation) 
support.getDropLocation();
+            DbImportTreeNode dropLocationParentNode = (DbImportTreeNode) 
dropLocation.getPath().getLastPathComponent();
+
+            DbImportTreeNode[] nodes = getNodesFromSupport(support);
+            if (nodes != null) {
+                DragAndDropNodeAction action = 
projectController.getApplication().getActionManager()
+                        .getAction(DragAndDropNodeAction.class);
+                action.setDropLocationParentNode(dropLocationParentNode);
+                action.setSourceParentNode(sourceParentNode);
+                action.setDropLocation(dropLocation);
+                action.setNodes(nodes);
+                action.setTree(targetTree);
+                action.performAction(null);
+                return true;
+            }
+            return false;
+        }
+
+        private DbImportTreeNode[] getNodesFromSupport(TransferSupport 
support) {
+            Transferable transferable = support.getTransferable();
+            DbImportTreeNode[] nodes = null;
+            try {
+                for (DataFlavor dataFlavor : 
transferable.getTransferDataFlavors()) {
+                    nodes = (DbImportTreeNode[]) 
transferable.getTransferData(dataFlavor);
+                }
+            } catch (IOException | UnsupportedFlavorException e) {
+                return null;
+            }
+            return nodes;
+        }
     }
 
     private class SourceTreeSelectionListener implements TreeSelectionListener 
{
@@ -418,4 +494,9 @@ public class DraggableTreePanel extends JScrollPane {
             }
         }
     }
+
+    private enum ImportSourceTree {
+        TARGET_TREE,
+        SOURCE_TREE
+    }
 }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/PrintColumnsBiFunction.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/PrintColumnsBiFunction.java
index be77314be..6781c8a8e 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/PrintColumnsBiFunction.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/PrintColumnsBiFunction.java
@@ -55,6 +55,6 @@ public class PrintColumnsBiFunction implements 
BiFunction<FilterContainer, DbImp
         dbImportTree.packColumns(tableFilter, container);
 
         container.setLoaded(true);
-        model.reload(container);
+        dbImportTree.reloadModelKeepingExpanded(container);
     }
 }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/PrintTablesBiFunction.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/PrintTablesBiFunction.java
index b547fb9a2..65f50b52d 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/PrintTablesBiFunction.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/PrintTablesBiFunction.java
@@ -39,7 +39,6 @@ public class PrintTablesBiFunction implements 
BiFunction<FilterContainer, DbImpo
 
     @Override
     public Void apply(FilterContainer filterContainer, DbImportTreeNode root) {
-        DbImportModel model = (DbImportModel) dbImportTree.getModel();
         boolean isTransferable = dbImportTree.isTransferable();
         if (root.getChildCount() != 0) {
             root.removeAllChildren();
@@ -58,7 +57,7 @@ public class PrintTablesBiFunction implements 
BiFunction<FilterContainer, DbImpo
             root.add(node);
             dbImportTree.packColumns(includeTable, node);
         }
-        model.reload(root);
+        dbImportTree.reloadModelKeepingExpanded(root);
 
         return null;
     }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/TreeToolbarPanel.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/TreeToolbarPanel.java
index 506cc2d05..00f8d406c 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/TreeToolbarPanel.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/TreeToolbarPanel.java
@@ -30,6 +30,7 @@ import 
org.apache.cayenne.dbsync.reverse.dbimport.ReverseEngineering;
 import org.apache.cayenne.dbsync.reverse.dbimport.Schema;
 import org.apache.cayenne.modeler.ProjectController;
 import org.apache.cayenne.modeler.action.GetDbConnectionAction;
+import org.apache.cayenne.modeler.action.SortNodesAction;
 import org.apache.cayenne.modeler.action.dbimport.AddCatalogAction;
 import org.apache.cayenne.modeler.action.dbimport.AddExcludeColumnAction;
 import org.apache.cayenne.modeler.action.dbimport.AddExcludeProcedureAction;
@@ -68,6 +69,7 @@ class TreeToolbarPanel extends JToolBar {
     private JButton editButton;
     private JButton deleteButton;
     private JButton configureButton;
+    private JButton sortButton;
     private DbImportTree reverseEngineeringTree;
 
     private Map<Class, List<JButton>> levels;
@@ -170,6 +172,7 @@ class TreeToolbarPanel extends JToolBar {
         this.add(includeProcedureButton);
         this.add(excludeProcedureButton);
         this.add(editButton);
+        this.add(sortButton);
         this.addSeparator();
         this.add(deleteButton);
         this.add(configureButton);
@@ -198,6 +201,23 @@ class TreeToolbarPanel extends JToolBar {
         deleteButton.setEnabled(true);
     }
 
+
+
+    private void createButtons(DraggableTreePanel panel) {
+        schemaButton = createButton(AddSchemaAction.class, 0);
+        catalogButton = createButton(AddCatalogAction.class, 0);
+        includeTableButton = createButton(AddIncludeTableAction.class, 1);
+        excludeTableButton = createButton(AddExcludeTableAction.class, 2, 
ExcludeTable.class);
+        includeColumnButton = createButton(AddIncludeColumnAction.class, 2, 
IncludeColumn.class);
+        excludeColumnButton = createButton(AddExcludeColumnAction.class, 2, 
ExcludeColumn.class);
+        includeProcedureButton = createButton(AddIncludeProcedureAction.class, 
2, IncludeProcedure.class);
+        excludeProcedureButton = createButton(AddExcludeProcedureAction.class, 
3, ExcludeProcedure.class);
+        sortButton = createButton(SortNodesAction.class,0);
+        editButton = createButton(EditNodeAction.class, 0);
+        deleteButton = createDeleteButton(panel);
+        configureButton = createConfigureButton();
+    }
+
     private <T extends TreeManipulationAction> JButton createButton(Class<T> 
actionClass, int position) {
         TreeManipulationAction action = 
projectController.getApplication().getActionManager().getAction(actionClass);
         action.setTree(reverseEngineeringTree);
@@ -211,21 +231,15 @@ class TreeToolbarPanel extends JToolBar {
         return action.buildButton(position);
     }
 
-    private void createButtons(DraggableTreePanel panel) {
-        schemaButton = createButton(AddSchemaAction.class, 0);
-        catalogButton = createButton(AddCatalogAction.class, 0);
-        includeTableButton = createButton(AddIncludeTableAction.class, 1);
-        excludeTableButton = createButton(AddExcludeTableAction.class, 2, 
ExcludeTable.class);
-        includeColumnButton = createButton(AddIncludeColumnAction.class, 2, 
IncludeColumn.class);
-        excludeColumnButton = createButton(AddExcludeColumnAction.class, 2, 
ExcludeColumn.class);
-        includeProcedureButton = createButton(AddIncludeProcedureAction.class, 
2, IncludeProcedure.class);
-        excludeProcedureButton = createButton(AddExcludeProcedureAction.class, 
3, ExcludeProcedure.class);
-        editButton = createButton(EditNodeAction.class, 0);
+    private JButton createConfigureButton() {
+        GetDbConnectionAction action = 
projectController.getApplication().getActionManager().getAction(GetDbConnectionAction.class);
+        return action.buildButton(0);
+    }
+
+    private JButton createDeleteButton(DraggableTreePanel panel) {
         DeleteNodeAction deleteNodeAction = 
projectController.getApplication().getActionManager().getAction(DeleteNodeAction.class);
         deleteNodeAction.setTree(reverseEngineeringTree);
         deleteNodeAction.setPanel(panel);
-        deleteButton = deleteNodeAction.buildButton(0);
-        GetDbConnectionAction action = 
projectController.getApplication().getActionManager().getAction(GetDbConnectionAction.class);
-        configureButton = action.buildButton(0);
+        return deleteNodeAction.buildButton(0);
     }
 }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/undo/DbImportTreeUndoableEdit.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/undo/DbImportTreeUndoableEdit.java
index 6fe77b051..2d775e4ae 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/undo/DbImportTreeUndoableEdit.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/undo/DbImportTreeUndoableEdit.java
@@ -27,7 +27,7 @@ import 
org.apache.cayenne.modeler.editor.dbimport.DbImportTree;
 import javax.swing.undo.AbstractUndoableEdit;
 import javax.swing.undo.CannotRedoException;
 import javax.swing.undo.CannotUndoException;
-import java.util.ArrayList;
+import java.util.List;
 
 /**
  * @since 4.1
@@ -56,7 +56,7 @@ public class DbImportTreeUndoableEdit extends 
AbstractUndoableEdit {
     public void redo() throws CannotRedoException {
         tree.stopEditing();
         tree.setReverseEngineering(this.nextReverseEngineering);
-        ArrayList<DbImportTreeNode> list = tree.getTreeExpandList();
+        List<DbImportTreeNode> list = tree.getTreeExpandList();
         
projectController.getApplication().getMetaData().add(projectController.getCurrentDataMap(),
 tree.getReverseEngineering());
         projectController.setDirty(true);
         tree.translateReverseEngineeringToTree(tree.getReverseEngineering(), 
false);
@@ -67,7 +67,7 @@ public class DbImportTreeUndoableEdit extends 
AbstractUndoableEdit {
     public void undo() throws CannotUndoException {
         tree.stopEditing();
         tree.setReverseEngineering(this.previousReverseEngineering);
-        ArrayList<DbImportTreeNode> list = tree.getTreeExpandList();
+        List<DbImportTreeNode> list = tree.getTreeExpandList();
         
projectController.getApplication().getMetaData().add(projectController.getCurrentDataMap(),
 tree.getReverseEngineering());
         projectController.setDirty(true);
         tree.translateReverseEngineeringToTree(tree.getReverseEngineering(), 
false);
diff --git 
a/modeler/cayenne-modeler/src/main/resources/org/apache/cayenne/modeler/images/icon-dbi-sort.png
 
b/modeler/cayenne-modeler/src/main/resources/org/apache/cayenne/modeler/images/icon-dbi-sort.png
new file mode 100644
index 000000000..f7dca9afe
Binary files /dev/null and 
b/modeler/cayenne-modeler/src/main/resources/org/apache/cayenne/modeler/images/icon-dbi-sort.png
 differ
diff --git 
a/modeler/cayenne-modeler/src/test/java/org/apache/cayenne/modeler/editor/dbimport/DbImportSorterTest.java
 
b/modeler/cayenne-modeler/src/test/java/org/apache/cayenne/modeler/editor/dbimport/DbImportSorterTest.java
index 50c57bf2a..a64ab86fa 100644
--- 
a/modeler/cayenne-modeler/src/test/java/org/apache/cayenne/modeler/editor/dbimport/DbImportSorterTest.java
+++ 
b/modeler/cayenne-modeler/src/test/java/org/apache/cayenne/modeler/editor/dbimport/DbImportSorterTest.java
@@ -47,22 +47,44 @@ public class DbImportSorterTest {
     }
 
     @Test
-    public void sortNodeTest(){
-        DbImportSorter.sortSingleNode(node);
+    public void sortByTypeByNameSingleNodeTest(){
+        
DbImportSorter.sortSingleNode(node,DbImportSorter.NODE_COMPARATOR_BY_TYPE_BY_NAME);
 
-        assertEquals("a", node.getChildNodes().get(0).getSimpleNodeName());
-        assertEquals("b", node.getChildNodes().get(1).getSimpleNodeName());
-        assertEquals("c", node.getChildNodes().get(2).getSimpleNodeName());
+        // DbImportTreeNode.ExpandableEnforcerNode at index 0
+        assertEquals("a", node.getChildNodes().get(1).getSimpleNodeName());
+        assertEquals("b", node.getChildNodes().get(2).getSimpleNodeName());
+        assertEquals("c", node.getChildNodes().get(3).getSimpleNodeName());
+    }
+
+    @Test
+    public void sortByTypeSingleNodeTest(){
+        
DbImportSorter.sortSingleNode(node,DbImportSorter.NODE_COMPARATOR_BY_TYPE);
+
+        // DbImportTreeNode.ExpandableEnforcerNode at index 0
+        assertEquals("a", node.getChildNodes().get(3).getSimpleNodeName());
+        assertEquals("b", node.getChildNodes().get(2).getSimpleNodeName());
+        assertEquals("c", node.getChildNodes().get(1).getSimpleNodeName());
     }
 
 
     @Test
-    public void sortNodeWithAllChildrenTest(){
-        DbImportSorter.sortSubtree(node);
+    public void sortByTypeByNameSubtreeTest(){
+        
DbImportSorter.sortSubtree(node,DbImportSorter.NODE_COMPARATOR_BY_TYPE_BY_NAME);
 
-        DbImportTreeNode tableNode = node.getChildNodes().get(0);
-        DbImportTreeNode columnNode = tableNode.getChildNodes().get(0);
+        // DbImportTreeNode.ExpandableEnforcerNode at index 0
+        DbImportTreeNode tableNode = node.getChildNodes().get(1);
+        DbImportTreeNode columnNode = tableNode.getChildNodes().get(1);
         assertEquals("1", columnNode.getSimpleNodeName());
     }
 
+    @Test
+    public void sortByTypeBySubtreeTest(){
+        
DbImportSorter.sortSubtree(node,DbImportSorter.NODE_COMPARATOR_BY_TYPE);
+
+        // DbImportTreeNode.ExpandableEnforcerNode at index 0
+        DbImportTreeNode tableNode = node.getChildNodes().get(3);
+        DbImportTreeNode columnNode = tableNode.getChildNodes().get(1);
+        assertEquals("2", columnNode.getSimpleNodeName());
+    }
+
 }


Reply via email to