Repository: joelittlejohn/jsonschema2pojo
Pull request #719 (2017-04-11 15:33:28)

Add 'sourceSortOrder' to decide how source files are iterated
…es' to process source files before directories.

We have noticed that when running the code generation on Windows and Linux we can inconsistencies of which packages included sub-classes were being created.  Given the source files:

`src/main/resources/virtualization-capabilities-api/schema/json/includes/MessageProperties.jsd`
`src/main/resources/virtualization-capabilities-api/schema/json/includes/VirtualMachineQuery.jsd`
`src/main/resources/virtualization-capabilities-api/schema/json/VMQueryRequestMessage.jsd`

On our Linux box it generates:

`target/generated-sources/virtualization-capabilities-api/com/dell/cpsd/vcenter/capabilities/api/MessageProperties.java`
`target/generated-sources/virtualization-capabilities-api/com/dell/cpsd/vcenter/capabilities/api/Query.java`
`target/generated-sources/virtualization-capabilities-api/com/dell/cpsd/vcenter/capabilities/api/VMQueryRequestMessage.java`


And on Windows it generates:
`target/generated-sources/virtualization-capabilities-api/com/dell/cpsd/vcenter/capabilities/api/includes/MessageProperties.java`
`target/generated-sources/virtualization-capabilities-api/com/dell/cpsd/vcenter/capabilities/api/includes/VirtualMachineQuery.java`
`target/generated-sources/virtualization-capabilities-api/com/dell/cpsd/vcenter/capabilities/api/VMQueryRequestMessage.java`

We're submitting this new configuration option to ensure that within a directory, files are processed first before any sub-directories to ensure consistency between environments.  The default `false` maintains the existing behaviour to ensure backward compatibility.

We could only provide an integration test for when the option is set to `true`, as in this case we can verify the behaviour, the `false` configuration option assertions would depend on the underlying OS.

joelittlejohn: Can you help me understand why the existing code which uses `Collections.sort(schemaFiles);` is not enough for you to get predictable results between OSs? 

I assume that you're using $refs without specifying javaType in your schemas, this isn't recommended as there are a variety of other ways in which your types can be read in a different order and hence generate different names.
Paul-Vincent: Hi @joelittlejohn Thanks for the fast response.  When investigating the issue we were seeing we did a small test using java.lang.File list files and then using `Collections.sort(schemaFiles)` and found the following:

On Linux:
Sorted list order:
`./src/main/resources/virtualization-capabilities-api/schema/json/VMQueryRequestMessage.jsd
./src/main/resources/virtualization-capabilities-api/schema/json/includes`

Revised sorted list order:
`./src/main/resources/virtualization-capabilities-api/schema/json/VMQueryRequestMessage.jsd
./src/main/resources/virtualization-capabilities-api/schema/json/includes`

On Windows:
Sorted list order:
`.\src\main\resources\virtualization-capabilities-api\schema\json\includes
.\src\main\resources\virtualization-capabilities-api\schema\json\VMQueryRequestMessage.jsd`

Revised sorted list order:
`.\src\main\resources\virtualization-capabilities-api\schema\json\VMQueryRequestMessage.jsd
.\src\main\resources\virtualization-capabilities-api\schema\json\includes`

Where the sorted list order is the result after `Collections.sort(schemaFiles)` and the revised sort order is when after applying the new `Comparator` implementation.

Thanks for highlighting around the javaType, we're exploring the possibility of submitting a further feature pull request to enable this to be relative to the target package.
ctrimble: Is there a reason we cannot just sort these files by their canonical paths?  It seems like that would be a much simpler change and should stabilize the sort order, since natural order is clearly defined for strings in Java.
Paul-Vincent: Hi @ctrimble Thanks for the comment.  I think we'd still see inconsistencies between file generation if we're exclusively sorting on the canonical path, we would see files sorted equally with sub directories, as in example above with the `includes` sub-directory as a peer to `VMQueryRequestMessage.jsd`. 

Given the scenario where we process the files as:
`/schemas/includes/CommonAttribute.jsd`
`/schemas/VMQueryRequestMessage.jsd`

We'll create:
`/target/includes/CommonAttribute.java`
`/target/VMQueryRequestMessage.java`

Given a subsequent addition of a new schema `AddNodes.jsd` which also references the `CommonAttribute` the files will be processed as:
`/schemas/AddNodes.jsd`
`/schemas/includes/CommonAttribute.jsd`
`/schemas/VMQueryRequestMessage.jsd`

This time creating:
`/target/AddNodes.jsd`
`/target/CommonAttribute.java`
`/target/VMQueryRequestMessage.java`

Note the package change.  The feature we're proposing will ensure that this scenario is covered, in that the first scenario will be processed as:
`/schemas/VMQueryRequestMessage.jsd`
`/schemas/includes/CommonAttribute.jsd`

Creating:
`/target/CommonAttribute.java`
`/target/VMQueryRequestMessage.java`

And then the second scenario will be processed as:
`/schemas/AddNodes.jsd`
`/schemas/VMQueryRequestMessage.jsd`
`/schemas/includes/CommonAttribute.jsd`

Creating:
`/target/AddNodes.jsd`
`/target/CommonAttribute.java`
`/target/VMQueryRequestMessage.java`

This goes some way toward helping out with workaround around:
https://github.com/joelittlejohn/jsonschema2pojo/issues/683
https://github.com/joelittlejohn/jsonschema2pojo/issues/164

ctrimble: @Paul-Vincent The sort order here is still going to be unstable.  The root cause is that files are visited in a different order on Unix systems and Windows systems, because Windows does not take case into account.  https://docs.oracle.com/javase/7/docs/api/java/io/File.html#compareTo(java.io.File)

This fix does not address root cause, as you are still going to have an unstable sort order.  For instance, the files `alpha.json`, `Beta.json`, `gamma.json` will not be visited in the same order on Unix and Windows.

There are issues with the mapping between source file locations and generated file locations in this project, but that problem is not being directly addressed by this PR.
Paul-Vincent: Hi @ctrimble,  The issue we're trying to resolve with this PR is to resolve the files and directories issue that we're seeing at the moment without the PR.  We can add to the PR to do a case insensitive sort within files and directories if you feel it would be use?
ctrimble: @Paul-Vincent I don't know what Joe wants to do with this, but I would not take this approach to solving the issue.  You are having incompatibility between the sort order of files on Windows and Linux, per the PR description.  That problem will still exist, in part, after the PR is applied.  Subdirectories with mixed case will still be visited in different order.  Files in the same directory with mixed case will still be visited in a different order.

Based of the examples offered and issues sited, it seems like the issues of inconsistent sort order and generated file location are being conflated.  In my opinion, each should be addressed individually, with the former being a bug in the comparator and the latter being a more systemic problem with the overall visit order of the schemas.
Paul-Vincent: Just in my opinion, the case used within files and directories is within the control of the user so shouldn't be much of a factor, even though it would be nice to be consistent.  By ensuring that directories are processed after files it means that we, as clients, can control the generated packages, we have means through the javaName/javaType to ensure the class name is as we expect. 

The biggest problem we're trying to address here is the package that classes are being generated within, our CI environment using Linux but some of our developers use Windows.
Paul-Vincent: @ctrimble @joelittlejohn Hi guys, please let me know what we can do to help progress this discussion.  We're coming up against some internal milestone dates, and we may need to work around this.  Thanks.
ctrimble: @Paul-Vincent I would feel comfortable merging a PR that was based on sorting files by `File. getCanonicalPath()`, along with a flag to turn back on sorting by `File.compareTo()`.  That would fix the issue with different naming on Linux and Windows, but allow people to get the current ordering if needed.
I don't see this PR as getting the project any closer to the principle of least surprise, with regard to where files end up.
Paul-Vincent: thanks @ctrimble So if we change the comparator to do a case insensitive sort first based on the canonical path, and ensure files are before directories we'll be good?  As an example resulting in:

`/schemas/alpha.jsd`
`/schemas/Beta.jsd`
`/schemas/gamma.jsd`
`/schemas/Lambda.jsd`
`/schemas/include/common_atttributes.jsd`
`/schemas/include/Common_Properties.jsd`
ctrimble: Without regard to the type of node (file or directory), but just on the canonical path.  The comparison should look something like this:

```
if( legacySortOrder ) {
  return fileA.compareTo(fileB);
} else {
  return fileA.getCanonicalPath().compareTo(fileB.getCanonicalPath());
}
```

That will give you the same set of generated files on Linux and Windows.  The flag will need to be a configuration option and will let those doing Windows only work keep the current functionality.
joelittlejohn: I think this is a good compromise and way forward to do something useful here that solves your problem. Without using javaType in your schemas you'll likely still have occasional surprises when editing your input, however if the result of this PR is at least that output can be made predictable _when the same exact project is built on different OSs_ then that will be a positive change. @ctrimble has identified that the current changes _do not_ solve the problem of OS-specific ordering and I agree that solving this should be the goal of this PR.

There seems to be one final question here about how to treat subdirectories. Sorting by canonical path is simple, and solves the problem of OS-specific output, but I don't think it it produces behaviour that is particularly useful or expected. I think dipping in and out of subdirectories and mixing files in the current directory with files in subdirectories as you iterate is a strange thing to do.

So I'd be happy with iteration order that uses: case-sensitive-sorted files followed by case-sensitive-sorted directories (or vice-versa).

The only question that remains for me is, which makes more sense, directories first or files first? I actually think directories first makes more sense, since I imagine that subdirectories contain shared things that schemas alongside those subdirectories will use. In this case, it's nicer for the naming of those items in subdirectories to be driven by their own filenames and not any $refs to them. Do you think the opposite is true? People generally put shared schemas in the root directory and then have subdirectories with schemas inside that use e.g. `"$ref": "../MyThing.json"`?

I'd actually be happy to call any configuration option we introduce here simply `sortInputFiles`, because I think that the current sorting (being OS-specific) isn't really a definitive sort. It will be obvious to any user of this config option that they are turning on _true_, definitive input sorting.  
ctrimble: Joe, this is your call, but the naming problem with `$ref`ed schemas is expressed by the visit order, not caused by it.  We would be well served by fixing the sort order by aligning with the order on one of the OSes and then fixing the `$ref` issue by directly addressing that problem.
joelittlejohn: @ctrimble Yes, I agree. I only mention issues with `$ref` to reiterate that there are still further ways in which naming could produce unexpected results, even if we create a 'predictable' visit order across OSs. I'd like to make whatever change is most useful here _with_ the limitations of $ref'd schemas in mind.

The more I think about this, the more I think that whatever strategy we choose here we'll see another issue or PR raised soon with an alternative requirement. I think my preferred solution would be:

```
<sourceSortOrder>OS<sourceSortOrder> <-- this is the default

<sourceSortOrder>FILES_FIRST<sourceSortOrder> <-- case-sensitive sort, visit files first

<sourceSortOrder>SUBDIRS_FIRST<sourceSortOrder> <-- case-sensitive sort, visit subdirs before files
```

We don't need to decide to align with any new sort order in particular because the current behaviour is going to become the default. If anyone wants any other strategy (and that strategy is sensible) then we can add it. I use the word 'source' here rather than 'input' because it fits with the naming convention for the other config arguments.

I think it's fair to say adding the fact that this option needs to exist at all is proof of bad decisions on my part back when this project started. It's a sticking-plaster for a much deeper problem but I'm happy to see this added as I think people would find it useful and it's fairly easy and simple to implement.
Paul-Vincent: Thanks for the feedback guys, it's a nice idea to cater for various sorting options. I'll update the pull request aa soon as I can, hopefully this weekend.
Paul-Vincent: Hi, thanks for the feedback @joelittlejohn and @ctrimble.  I've updated the configuration option, removing the previously added `processSourceFiledBeforeDirectories` with the suggestion of `sourceSortOrder` configuration option with the options of `OS`, `SUBDIRS_FIRST` or `FILES_FIRST`.

Please note that this is a case sensitive sort as per @joelittlejohn last comment.
joelittlejohn: Great stuff Paul, and thanks to @ctrimble for valuable input too.

I'm on vacation right now but will look at this as soon as I return (which
is Friday).


On Tuesday, 18 April 2017, Paul Vincent <notifications@github.com> wrote:

> Hi, thanks for the feedback @joelittlejohn
> <https://github.com/joelittlejohn> and @ctrimble
> <https://github.com/ctrimble>. I've updated the configuration option,
> removing the previously added processSourceFiledBeforeDirectories with
> the suggestion of sourceSortOrder configuration option with the options
> of OS, SUBDIRS_FIRST or FILES_FIRST.
>
> Please note that this is a case sensitive sort as per @joelittlejohn
> <https://github.com/joelittlejohn> last comment.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/joelittlejohn/jsonschema2pojo/pull/719#issuecomment-294927453>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AAp5MIsNoTTsWc6nD1d9D9z3C4wfcKmjks5rxPhSgaJpZM4M6RWR>
> .
>

joelittlejohn: Based on a quick browse I like the approach you've taken to implement this.
Your changes look thorough and so I expect this can be merged quickly.

One minor comment: could you fix the places where code style is not
consistent with this project?

* Opening braces should be on the same line
* Regular imports should not use wildcards
* Static imports should always use wildcards.

This helps to avoid ping-pong style edits where each author is reorganising
the imports.



On Tuesday, 18 April 2017, Joe Littlejohn <joelittlejohn@gmail.com> wrote:

> Great stuff Paul, and thanks to @ctrimble for valuable input too.
>
> I'm on vacation right now but will look at this as soon as I return (which
> is Friday).
>
>
> On Tuesday, 18 April 2017, Paul Vincent <notifications@github.com
> <javascript:_e(%7B%7D,'cvml','notifications@github.com');>> wrote:
>
>> Hi, thanks for the feedback @joelittlejohn
>> <https://github.com/joelittlejohn> and @ctrimble
>> <https://github.com/ctrimble>. I've updated the configuration option,
>> removing the previously added processSourceFiledBeforeDirectories with
>> the suggestion of sourceSortOrder configuration option with the options
>> of OS, SUBDIRS_FIRST or FILES_FIRST.
>>
>> Please note that this is a case sensitive sort as per @joelittlejohn
>> <https://github.com/joelittlejohn> last comment.
>>
>> —
>> You are receiving this because you were mentioned.
>> Reply to this email directly, view it on GitHub
>> <https://github.com/joelittlejohn/jsonschema2pojo/pull/719#issuecomment-294927453>,
>> or mute the thread
>> <https://github.com/notifications/unsubscribe-auth/AAp5MIsNoTTsWc6nD1d9D9z3C4wfcKmjks5rxPhSgaJpZM4M6RWR>
>> .
>>
>

Paul-Vincent: Great thanks @joelittlejohn.  I fully understand about avoiding the ping-pong edits,  I've updated the formatting and the imports as requested.
Enjoy your vacation.

Patch:
diff --git a/jsonschema2pojo-ant/src/main/java/org/jsonschema2pojo/ant/Jsonschema2PojoTask.java b/jsonschema2pojo-ant/src/main/java/org/jsonschema2pojo/ant/Jsonschema2PojoTask.java
index 0ff575c7..780c292f 100644
--- a/jsonschema2pojo-ant/src/main/java/org/jsonschema2pojo/ant/Jsonschema2PojoTask.java
+++ b/jsonschema2pojo-ant/src/main/java/org/jsonschema2pojo/ant/Jsonschema2PojoTask.java
@@ -42,6 +42,7 @@ import org.jsonschema2pojo.GenerationConfig;
 import org.jsonschema2pojo.InclusionLevel;
 import org.jsonschema2pojo.Jsonschema2Pojo;
 import org.jsonschema2pojo.NoopAnnotator;
+import org.jsonschema2pojo.SourceSortOrder;
 import org.jsonschema2pojo.SourceType;
 import org.jsonschema2pojo.URLProtocol;
 import org.jsonschema2pojo.rules.RuleFactory;
@@ -154,6 +155,8 @@ public class Jsonschema2PojoTask extends Task implements GenerationConfig {
 
     private String refFragmentPathDelimiters = "#/.";
 
+    private SourceSortOrder sourceSortOrder = SourceSortOrder.OS;
+
     /**
      * Execute this task (it's expected that all relevant setters will have been
      * called by Ant to provide task configuration <em>before</em> this method
@@ -729,7 +732,7 @@ public class Jsonschema2PojoTask extends Task implements GenerationConfig {
     /**
      * Sets the 'customDateTimePattern' property of this class
      *
-     * @param customDatePattern
+     * @param customDateTimePattern
      *            A custom pattern to use when formatting date-time fields during
      *            serialization. Requires support from your JSON binding
      *            library.
@@ -749,6 +752,16 @@ public class Jsonschema2PojoTask extends Task implements GenerationConfig {
         this.refFragmentPathDelimiters = refFragmentPathDelimiters;
     }
 
+    /**
+     * Sets the 'sourceSortOrder' property of this class
+     *
+     * @param sourceSortOrder Sets the sort order for the source files to be processed in.  By default the OS can
+     *                        influence the processing order.
+     */
+    public void setSourceSortOrder(SourceSortOrder sourceSortOrder) {
+        this.sourceSortOrder = sourceSortOrder;
+    }
+
     @Override
     public boolean isGenerateBuilders() {
         return generateBuilders;
@@ -1010,4 +1023,9 @@ public class Jsonschema2PojoTask extends Task implements GenerationConfig {
     public String getRefFragmentPathDelimiters() {
         return refFragmentPathDelimiters;
     }
+
+    @Override
+    public SourceSortOrder getSourceSortOrder() {
+        return sourceSortOrder;
+    }
 }
diff --git a/jsonschema2pojo-ant/src/site/Jsonschema2PojoTask.html b/jsonschema2pojo-ant/src/site/Jsonschema2PojoTask.html
index 013bbc94..87ddd105 100644
--- a/jsonschema2pojo-ant/src/site/Jsonschema2PojoTask.html
+++ b/jsonschema2pojo-ant/src/site/Jsonschema2PojoTask.html
@@ -431,6 +431,23 @@
         </td>
         <td align="center" valign="top">No (default <code>#/.</code>)</td>
     </tr>
+    <tr>
+        <td valign="top">sourceSortOrder</td>
+        <td valign="top">The sort order to be applied to the source files when being processed, by default the OS can
+            influence the ordering.
+            <ul>
+                <li><code>OS</code> (Let the OS influence the order that the source files are processed.)</li>
+                <li><code>FILES_FIRST</code> (Case sensitive sort, visit the files first. The source files are processed
+                    in a breadth  first sort order.)
+                </li>
+                <li><code>SUBDIRS_FIRST</code> (Case sensitive sort, visit the sub-directories before the files. The
+                    source files are processed in a depth first sort order.)
+                </li>
+            </ul>
+        </td>
+        <td align="center" valign="top">No (default <code>OS</code>)</td>
+    </tr>
+
 </table>
 
 <h3>Examples</h3>
diff --git a/jsonschema2pojo-cli/src/main/java/org/jsonschema2pojo/cli/Arguments.java b/jsonschema2pojo-cli/src/main/java/org/jsonschema2pojo/cli/Arguments.java
index 5296f51b..b3e5bd4f 100644
--- a/jsonschema2pojo-cli/src/main/java/org/jsonschema2pojo/cli/Arguments.java
+++ b/jsonschema2pojo-cli/src/main/java/org/jsonschema2pojo/cli/Arguments.java
@@ -30,6 +30,7 @@ import org.jsonschema2pojo.Annotator;
 import org.jsonschema2pojo.GenerationConfig;
 import org.jsonschema2pojo.InclusionLevel;
 import org.jsonschema2pojo.NoopAnnotator;
+import org.jsonschema2pojo.SourceSortOrder;
 import org.jsonschema2pojo.SourceType;
 import org.jsonschema2pojo.rules.RuleFactory;
 
@@ -181,9 +182,13 @@ public class Arguments implements GenerationConfig {
     @Parameter(names = {"-rpd", "--ref-fragment-path-delimiters"}, description = "A string containing any characters that should act as path delimiters when resolving $ref fragments. By default, #, / and . are used in an attempt to support JSON Pointer and JSON Path.")
     private String refFragmentPathDelimiters = "#/.";
 
+    @Parameter(names = { "-sso", "--source-sort-order" }, description = "The sort order to be applied to the source files.  Available options are: OS, FILES_FIRST or SUBDIRS_FIRST")
+    private SourceSortOrder sourceSortOrder = SourceSortOrder.OS;
+
     private static final int EXIT_OKAY = 0;
     private static final int EXIT_ERROR = 1;
 
+
     /**
      * Parses command line arguments and populates this command line instance.
      * <p>
@@ -450,4 +455,8 @@ public class Arguments implements GenerationConfig {
         return customDateTimePattern;
     }
 
+    @Override
+    public SourceSortOrder getSourceSortOrder() {
+        return sourceSortOrder;
+    }
 }
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/DefaultGenerationConfig.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/DefaultGenerationConfig.java
index b0177728..2394a581 100644
--- a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/DefaultGenerationConfig.java
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/DefaultGenerationConfig.java
@@ -362,4 +362,12 @@ public class DefaultGenerationConfig implements GenerationConfig {
     public String getCustomDateTimePattern() {
         return null;
     }
-}
+
+    /**
+     * @return {@link SourceSortOrder#OS}
+     */
+    @Override
+    public SourceSortOrder getSourceSortOrder() {
+        return SourceSortOrder.OS;
+    }
+}
\ No newline at end of file
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/GenerationConfig.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/GenerationConfig.java
index 94331564..882d029b 100644
--- a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/GenerationConfig.java
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/GenerationConfig.java
@@ -475,4 +475,20 @@ public interface GenerationConfig {
      */
     String getRefFragmentPathDelimiters();
 
+    /**
+     * Gets the 'sourceSortOrder' configuration option.
+     *
+     * @return
+     *  <p>
+     *         Supported values:
+     *         <ul>
+     *         <li><code>OS</code> (Let the OS influence the order the source files are processed.)</li>
+     *         <li><code>FILES_FIRST</code> (Case sensitive sort, visit the files first.  The source files are processed in a breadth
+     *         first sort order.)</li>
+     *         <li><code>SUBDIRS_FIRST</code> (Case sensitive sort, visit the sub-directories before the files.  The source files are
+     *         processed in a depth first sort order.)</li>
+     *         </ul>
+     */
+    SourceSortOrder getSourceSortOrder();
+
 }
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/Jsonschema2Pojo.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/Jsonschema2Pojo.java
index 55d77013..0a93d62c 100644
--- a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/Jsonschema2Pojo.java
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/Jsonschema2Pojo.java
@@ -102,7 +102,8 @@ public class Jsonschema2Pojo {
     }
 
     private static void generateRecursive(GenerationConfig config, SchemaMapper mapper, JCodeModel codeModel, String packageName, List<File> schemaFiles) throws IOException {
-        Collections.sort(schemaFiles);
+
+        Collections.sort(schemaFiles, config.getSourceSortOrder().getComparator());
 
         for (File child : schemaFiles) {
             if (child.isFile()) {
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/SourceSortOrder.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/SourceSortOrder.java
new file mode 100644
index 00000000..8e155c33
--- /dev/null
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/SourceSortOrder.java
@@ -0,0 +1,88 @@
+/**
+ * Copyright Â© 2010-2014 Nokia
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jsonschema2pojo;
+
+import java.io.File;
+import java.util.Comparator;
+
+/**
+ * Defines the order the source files and directories are processed in.
+ */
+public enum SourceSortOrder {
+    /**
+     * <code>OS</code> Let the OS influence the order that the source files are processed.
+     */
+    OS {
+        @Override
+        public Comparator<File> getComparator() {
+            return new Comparator<File>() {
+                @Override
+                public int compare(final File fileA, final File fileB) {
+                    return fileA.compareTo(fileB);
+                }
+            };
+        }
+    },
+
+    /**
+     * <code>FILES_FIRST</code> Case sensitive sort, visit the files first.  The source files are processed in a
+     * breadth first sort order.
+     */
+    FILES_FIRST {
+        @Override
+        public Comparator<File> getComparator() {
+            return new Comparator<File>() {
+                @Override
+                public int compare(final File fileA, final File fileB) {
+                    if (fileA.isDirectory() && !fileB.isDirectory()) {
+                        return 1;
+                    }
+
+                    if (!fileA.isDirectory() && fileB.isDirectory()) {
+                        return -1;
+                    }
+                    return fileA.compareTo(fileB);
+                }
+            };
+        }
+    },
+
+    /**
+     * <code>SUBDIRS_FIRST</code> Case sensitive sort, visit the sub-directories before the files.  The source files
+     * are processed in a depth first sort order.
+     */
+    SUBDIRS_FIRST {
+        @Override
+        public Comparator<File> getComparator() {
+            return new Comparator<File>() {
+                @Override
+                public int compare(final File fileA, final File fileB) {
+                    if (fileA.isDirectory() && !fileB.isDirectory()) {
+                        return -1;
+                    }
+
+                    if (!fileA.isDirectory() && fileB.isDirectory()) {
+                        return 1;
+                    }
+                    return fileA.compareTo(fileB);
+                }
+            };
+        }
+    };
+
+    public abstract Comparator<File> getComparator();
+}
diff --git a/jsonschema2pojo-gradle-plugin/src/main/groovy/org/jsonschema2pojo/gradle/JsonSchemaExtension.groovy b/jsonschema2pojo-gradle-plugin/src/main/groovy/org/jsonschema2pojo/gradle/JsonSchemaExtension.groovy
index 5163ae15..4304da1f 100644
--- a/jsonschema2pojo-gradle-plugin/src/main/groovy/org/jsonschema2pojo/gradle/JsonSchemaExtension.groovy
+++ b/jsonschema2pojo-gradle-plugin/src/main/groovy/org/jsonschema2pojo/gradle/JsonSchemaExtension.groovy
@@ -21,6 +21,7 @@ import org.jsonschema2pojo.AllFileFilter
 import org.jsonschema2pojo.GenerationConfig
 import org.jsonschema2pojo.InclusionLevel
 import org.jsonschema2pojo.NoopAnnotator
+import org.jsonschema2pojo.SourceSortOrder
 import org.jsonschema2pojo.SourceType
 import org.jsonschema2pojo.rules.RuleFactory
 
@@ -77,6 +78,7 @@ public class JsonSchemaExtension implements GenerationConfig {
   String customDatePattern
   String customDateTimePattern
   String refFragmentPathDelimiters
+  SourceSortOrder sourceSortOrder
 
   public JsonSchemaExtension() {
     // See DefaultGenerationConfig
@@ -122,6 +124,7 @@ public class JsonSchemaExtension implements GenerationConfig {
     formatDates = false
     formatDateTimes = false
     refFragmentPathDelimiters = "#/."
+    sourceSortOrder = SourceSortOrder.OS
   }
 
   @Override
@@ -166,6 +169,10 @@ public class JsonSchemaExtension implements GenerationConfig {
     sourceType = SourceType.valueOf(s.toUpperCase())
   }
 
+  public void setSourceSortOrder(String sortOrder) {
+    sourceSortOrder = SourceSortOrder.valueOf(sortOrder.toUpperCase())
+  }
+
   @Override
   public String toString() {
     """|generateBuilders = ${generateBuilders}
@@ -211,6 +218,7 @@ public class JsonSchemaExtension implements GenerationConfig {
        |customDatePattern = ${customDatePattern}
        |customDateTimePattern = ${customDateTimePattern}
        |refFragmentPathDelimiters = ${refFragmentPathDelimiters}
+       |sourceSortOrder = ${sourceSortOrder}
      """.stripMargin()
   }
   
diff --git a/jsonschema2pojo-maven-plugin/src/main/java/org/jsonschema2pojo/maven/Jsonschema2PojoMojo.java b/jsonschema2pojo-maven-plugin/src/main/java/org/jsonschema2pojo/maven/Jsonschema2PojoMojo.java
index ae52f95f..496421a1 100644
--- a/jsonschema2pojo-maven-plugin/src/main/java/org/jsonschema2pojo/maven/Jsonschema2PojoMojo.java
+++ b/jsonschema2pojo-maven-plugin/src/main/java/org/jsonschema2pojo/maven/Jsonschema2PojoMojo.java
@@ -40,6 +40,7 @@ import org.jsonschema2pojo.GenerationConfig;
 import org.jsonschema2pojo.InclusionLevel;
 import org.jsonschema2pojo.Jsonschema2Pojo;
 import org.jsonschema2pojo.NoopAnnotator;
+import org.jsonschema2pojo.SourceSortOrder;
 import org.jsonschema2pojo.SourceType;
 import org.jsonschema2pojo.rules.RuleFactory;
 import org.jsonschema2pojo.util.URLUtil;
@@ -617,6 +618,23 @@ public class Jsonschema2PojoMojo extends AbstractMojo implements GenerationConfi
 
     private FileFilter fileFilter = new AllFileFilter();
 
+    /**
+     * The sort order to be applied when recursively processing the source
+     * files.  By default the OS can influence the processing order.   Supported values:
+     * <ul>
+     * <li><code>OS</code> (Let the OS influence the order the source files are processed.)</li>
+     * <li><code>FILES_FIRST</code> (Case sensitive sort, visit the files first.  The source files are processed in a breadth
+     * first sort order.)</li>
+     * <li><code>SUBDIRS_FIRST</code> (Case sensitive sort, visit the sub-directories before the files.  The source files are
+     * processed in a depth first sort order.)</li>
+     * </ul>
+     *
+     * @parameter expression="${jsonschema2pojo.sourceSortOrder}"
+     * default-value="OS"
+     * @since 0.4.34
+     */
+    private String sourceSortOrder = SourceSortOrder.OS.toString();
+
     /**
      * Executes the plugin, to read the given source and behavioural properties
      * and generate POJOs. The current implementation acts as a wrapper around
@@ -973,4 +991,9 @@ public class Jsonschema2PojoMojo extends AbstractMojo implements GenerationConfi
         return refFragmentPathDelimiters;
     }
 
+    @Override
+    public SourceSortOrder getSourceSortOrder()
+    {
+        return SourceSortOrder.valueOf(sourceSortOrder.toUpperCase());
+    }
 }


Test patch:
diff --git a/jsonschema2pojo-core/src/test/java/org/jsonschema2pojo/SourceSortOrderTest.java b/jsonschema2pojo-core/src/test/java/org/jsonschema2pojo/SourceSortOrderTest.java
new file mode 100644
index 00000000..3426a96d
--- /dev/null
+++ b/jsonschema2pojo-core/src/test/java/org/jsonschema2pojo/SourceSortOrderTest.java
@@ -0,0 +1,106 @@
+/**
+ * Copyright Â© 2010-2014 Nokia
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jsonschema2pojo;
+
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Comparator;
+
+import static org.hamcrest.MatcherAssert.*;
+import static org.hamcrest.Matchers.*;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+
+public class SourceSortOrderTest {
+    @Test
+    public void testTwoFilesAreCompared_FILES_FIRST() throws IOException {
+        testTwoFilesAreCompared(SourceSortOrder.FILES_FIRST.getComparator());
+    }
+
+    @Test
+    public void twoDirectoriesAreCompared_FILES_FIRST() throws IOException {
+        testTwoDirectoriesAreCompared(SourceSortOrder.FILES_FIRST.getComparator());
+    }
+
+    @Test
+    public void testTwoFilesAreCompared_SUBDIRS_FIRST() throws IOException {
+        testTwoFilesAreCompared(SourceSortOrder.SUBDIRS_FIRST.getComparator());
+    }
+
+    @Test
+    public void twoDirectoriesAreCompared_SUBDIRS_FIRST() throws IOException {
+        testTwoDirectoriesAreCompared(SourceSortOrder.SUBDIRS_FIRST.getComparator());
+    }
+
+    private void testTwoFilesAreCompared(Comparator<File> fileComparator) throws IOException {
+        final File mockFileA = mockFile();
+        final File mockFileB = mockFile();
+
+        fileComparator.compare(mockFileA, mockFileB);
+        verify(mockFileA, atLeast(1)).compareTo(mockFileB);
+    }
+
+    private void testTwoDirectoriesAreCompared(Comparator<File> fileComparator) throws IOException {
+        final File mockDirA = mockDirectory();
+        final File mockDirB = mockDirectory();
+
+        fileComparator.compare(mockDirA, mockDirB);
+        verify(mockDirA, atLeast(1)).compareTo(mockDirB);
+    }
+
+    @Test
+    public void filesBeforeDirectories_FILES_FIRST() throws IOException {
+        final Comparator<File> fileComparator = SourceSortOrder.FILES_FIRST.getComparator();
+        final File mockFile = mockFile();
+        final File mockDir = mockDirectory();
+
+        assertThat(fileComparator.compare(mockFile, mockDir), lessThan(0));
+        assertThat(fileComparator.compare(mockDir, mockFile), greaterThan(0));
+
+        verify(mockFile, never()).compareTo(any(File.class));
+        verify(mockDir, never()).compareTo(any(File.class));
+    }
+
+    @Test
+    public void filesBeforeDirectories_SUBDIRS_FIRST() throws IOException {
+        final Comparator<File> fileComparator = SourceSortOrder.SUBDIRS_FIRST.getComparator();
+        final File mockFile = mockFile();
+        final File mockDir = mockDirectory();
+
+        assertThat(fileComparator.compare(mockFile, mockDir), greaterThan(0));
+        assertThat(fileComparator.compare(mockDir, mockFile), lessThan(0));
+
+        verify(mockFile, never()).compareTo(any(File.class));
+        verify(mockDir, never()).compareTo(any(File.class));
+    }
+
+    private File mockFile() {
+        return mockFile(false);
+    }
+
+    private File mockDirectory() {
+        return mockFile(true);
+    }
+
+    private File mockFile(final boolean isDirectory) {
+        final File mockFile = mock(File.class);
+        when(mockFile.isDirectory()).thenReturn(isDirectory);
+        return mockFile;
+    }
+}
diff --git a/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/config/SourceSortOrderIT.java b/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/config/SourceSortOrderIT.java
new file mode 100644
index 00000000..22fd2ca0
--- /dev/null
+++ b/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/config/SourceSortOrderIT.java
@@ -0,0 +1,85 @@
+/**
+ * Copyright Â© 2010-2014 Nokia
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jsonschema2pojo.integration.config;
+
+import org.jsonschema2pojo.SourceSortOrder;
+import org.jsonschema2pojo.integration.util.Jsonschema2PojoRule;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+
+import static org.jsonschema2pojo.integration.util.CodeGenerationHelper.*;
+import static org.junit.Assert.*;
+
+public class SourceSortOrderIT {
+
+    @Rule
+    public Jsonschema2PojoRule schemaRule = new Jsonschema2PojoRule();
+
+    @Test
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    public void generatedClassesInCorrectPackageForFilesFirstSort() throws ClassNotFoundException, SecurityException,
+            NoSuchMethodException {
+
+        ClassLoader resultsClassLoader = schemaRule.generateAndCompile(
+                "/schema/sourceSortOrder/", "com.example", config("sourceSortOrder", SourceSortOrder.FILES_FIRST
+                        .toString()));
+
+        Class generatedTypeA = resultsClassLoader.loadClass("com.example.A");
+        Class generatedTypeZ = resultsClassLoader.loadClass("com.example.Z");
+
+        Method getterTypeA = generatedTypeA.getMethod("getRefToA");
+        final Class<?> returnTypeA = getterTypeA.getReturnType();
+
+        Method getterTypeZ = generatedTypeZ.getMethod("getRefToZ");
+        final Class<?> returnTypeZ = getterTypeZ.getReturnType();
+
+        assertInPackage("com.example", generatedTypeA);
+        assertInPackage("com.example", generatedTypeZ);
+        assertInPackage("com.example", returnTypeA);
+        assertInPackage("com.example", returnTypeZ);
+    }
+
+    @Test
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    public void generatedClassesInCorrectPackageForDirectoriesFirstSort() throws ClassNotFoundException,
+            SecurityException, NoSuchMethodException {
+
+        ClassLoader resultsClassLoader = schemaRule.generateAndCompile(
+                "/schema/sourceSortOrder/", "com.example", config("sourceSortOrder", SourceSortOrder.SUBDIRS_FIRST
+                        .toString()));
+
+        Class generatedTypeA = resultsClassLoader.loadClass("com.example.A");
+        Class generatedTypeZ = resultsClassLoader.loadClass("com.example.Z");
+
+        Method getterTypeA = generatedTypeA.getMethod("getRefToA");
+        final Class<?> returnTypeA = getterTypeA.getReturnType();
+
+        Method getterTypeZ = generatedTypeZ.getMethod("getRefToZ");
+        final Class<?> returnTypeZ = getterTypeZ.getReturnType();
+
+        assertInPackage("com.example", generatedTypeA);
+        assertInPackage("com.example", generatedTypeZ);
+        assertInPackage("com.example.includes", returnTypeA);
+        assertInPackage("com.example.includes", returnTypeZ);
+    }
+
+    private void assertInPackage(String expectedPackage, Class<?> generatedClass) {
+        assertEquals("Unexpected package", expectedPackage, generatedClass.getPackage().getName());
+    }
+}
diff --git a/jsonschema2pojo-integration-tests/src/test/resources/schema/sourceSortOrder/a.json b/jsonschema2pojo-integration-tests/src/test/resources/schema/sourceSortOrder/a.json
new file mode 100644
index 00000000..d477781e
--- /dev/null
+++ b/jsonschema2pojo-integration-tests/src/test/resources/schema/sourceSortOrder/a.json
@@ -0,0 +1,8 @@
+{
+    "type" : "object",
+    "properties" : {
+        "refToA" : {
+            "$ref": "includes/aType.json"
+        }
+    }
+}
\ No newline at end of file
diff --git a/jsonschema2pojo-integration-tests/src/test/resources/schema/sourceSortOrder/includes/aType.json b/jsonschema2pojo-integration-tests/src/test/resources/schema/sourceSortOrder/includes/aType.json
new file mode 100644
index 00000000..2d14b449
--- /dev/null
+++ b/jsonschema2pojo-integration-tests/src/test/resources/schema/sourceSortOrder/includes/aType.json
@@ -0,0 +1,8 @@
+{
+    "type" : "object",
+    "properties" : {
+        "propertyOfA" : {
+            "type" : "string"
+        }
+    }
+}
\ No newline at end of file
diff --git a/jsonschema2pojo-integration-tests/src/test/resources/schema/sourceSortOrder/includes/zType.json b/jsonschema2pojo-integration-tests/src/test/resources/schema/sourceSortOrder/includes/zType.json
new file mode 100644
index 00000000..f9d24276
--- /dev/null
+++ b/jsonschema2pojo-integration-tests/src/test/resources/schema/sourceSortOrder/includes/zType.json
@@ -0,0 +1,8 @@
+{
+    "type" : "object",
+    "properties" : {
+        "propertyOfZ" : {
+            "type" : "string"
+        }
+    }
+}
\ No newline at end of file
diff --git a/jsonschema2pojo-integration-tests/src/test/resources/schema/sourceSortOrder/z.json b/jsonschema2pojo-integration-tests/src/test/resources/schema/sourceSortOrder/z.json
new file mode 100644
index 00000000..03ad9354
--- /dev/null
+++ b/jsonschema2pojo-integration-tests/src/test/resources/schema/sourceSortOrder/z.json
@@ -0,0 +1,11 @@
+{
+    "type" : "object",
+    "properties" : {
+        "refToA" : {
+            "$ref": "includes/aType.json"
+        },
+        "refToZ" : {
+            "$ref" : "includes/zType.json"
+        }
+    }
+}
\ No newline at end of file