3939import java .nio .charset .StandardCharsets ;
4040import java .security .MessageDigest ;
4141import java .security .NoSuchAlgorithmException ;
42- import java .text .SimpleDateFormat ;
42+ import java .time .ZoneId ;
43+ import java .time .ZonedDateTime ;
44+ import java .time .format .DateTimeFormatter ;
45+ import java .time .format .FormatStyle ;
46+ import java .util .ArrayList ;
47+ import java .util .Comparator ;
4348import java .util .Date ;
49+ import java .util .List ;
4450import java .util .Locale ;
4551import java .util .Map ;
4652
@@ -109,32 +115,72 @@ public static StringBuilder renderDirectoryListing(final HttpServerExchange exch
109115 path += "/" ;
110116 }
111117
112- String relative = null ;
118+ String relative = determineRelativePath (exchange , path );
119+
120+ String sortColumn = "name" ;
121+ String currentSortOrder = "asc" ;
122+
113123 if (exchange != null ) {
114- final Map <String , Object > context = exchange .getAttachment (Predicate .PREDICATE_CONTEXT );
115- if (context != null ) {
116- final PathPrefixPredicate .PathPrefixMatchRecord trans = (PathPrefixMatchRecord ) context
117- .get (PathPrefixPredicate .PREFIX_MATCH_RECORD );
118- if (trans != null ) {
119- if (trans .isOverWritten ()) {
120- relative = trans .getPrefix ();
121- if (!relative .endsWith ("/" ) && !path .startsWith ("/" )) {
122- relative += "/" ;
123- }
124- }
125- }
124+ if (exchange .getQueryParameters ().get ("sort" ) != null ) {
125+ sortColumn = exchange .getQueryParameters ().get ("sort" ).getFirst ();
126+ }
127+ if (exchange .getQueryParameters ().get ("order" ) != null ) {
128+ currentSortOrder = exchange .getQueryParameters ().get ("order" ).getFirst ();
126129 }
127130 }
128131
129- StringBuilder builder = new StringBuilder ();
130- builder .append ("<html>\n <head>\n <script src='" ).append (relative == null ? path : relative + path ).append ("?js'></script>\n " )
131- .append ("<link rel='stylesheet' type='text/css' href='" ).append (relative == null ? path : relative + path ).append ("?css' />\n </head>\n " );
132- builder .append ("<body onresize='growit()' onload='growit()'>\n <table id='thetable'>\n <thead>\n " );
133- builder .append ("<tr><th class='loc' colspan='3'>Directory Listing - " ).append (relative == null ? path : relative + path ).append ("</th></tr>\n " )
134- .append ("<tr><th class='label offset'>Name</th><th class='label'>Last Modified</th><th class='label'>Size</th></tr>\n </thead>\n " )
135- .append ("<tfoot>\n <tr><th class=\" loc footer\" colspan=\" 3\" >Powered by Undertow</th></tr>\n </tfoot>\n <tbody>\n " );
132+ String newSortOrder = "asc" .equals (currentSortOrder ) ? "desc" : "asc" ;
133+ String sortUrl = relative == null ? path : relative + path ;
134+
135+ StringBuilder builder = buildDirectoryListingTable (sortUrl , sortColumn , newSortOrder );
136136
137137 int state = 0 ;
138+ String parent = getParentPath (path , state );
139+
140+ int i = 0 ;
141+ if (parent != null ) {
142+ i ++;
143+ appendParentDirectory (resource , builder , relative , parent );
144+ }
145+
146+ List <Resource > directories = new ArrayList <>();
147+ List <Resource > files = new ArrayList <>();
148+ separateDirectoriesAndFiles (resource , directories , files );
149+
150+ Comparator <Resource > comparator = getComparator (sortColumn , currentSortOrder );
151+ directories .sort (comparator );
152+ files .sort (comparator );
153+
154+ appendDirectories (directories , builder , i , sortUrl );
155+ appendFiles (files , builder , i , sortUrl );
156+
157+ builder .append ("</tbody>\n </table>\n </body>\n </html>" );
158+
159+ return builder ;
160+
161+ }
162+
163+ private static String formatLastModified (Date lastModified ) {
164+ if (lastModified == null ) {
165+ return "-" ;
166+ }
167+ ZonedDateTime lastModifiedTime = ZonedDateTime .ofInstant (
168+ lastModified .toInstant (),
169+ ZoneId .systemDefault ()
170+ );
171+ DateTimeFormatter formatter = DateTimeFormatter .ofLocalizedDateTime (FormatStyle .MEDIUM )
172+ .withLocale (Locale .getDefault ());
173+
174+ return formatter .format (lastModifiedTime );
175+ }
176+
177+ private static void appendParentDirectory (Resource resource , StringBuilder builder , String relative , String parent ) {
178+ builder .append ("<tr class='odd'><td><a class='icon up' href='" ).append (relative == null ? parent : relative + parent ).append (parent .endsWith ("/" ) ? "" : "/" ).append ("'>[..]</a></td><td>" );
179+ builder .append (formatLastModified (resource .getLastModified ()))
180+ .append ("</td><td>--</td></tr>\n " );
181+ }
182+
183+ private static String getParentPath (String path , int state ) {
138184 String parent = null ;
139185 if (path .length () > 1 ) {
140186 for (int i = path .length () - 1 ; i >= 0 ; i --) {
@@ -154,33 +200,89 @@ public static StringBuilder renderDirectoryListing(final HttpServerExchange exch
154200 parent = "/" ;
155201 }
156202 }
203+ return parent ;
204+ }
157205
158- SimpleDateFormat format = new SimpleDateFormat ("MMM dd, yyyy HH:mm:ss" , Locale .US );
159- int i = 0 ;
160- if (parent != null ) {
161- i ++;
162- builder .append ("<tr class='odd'><td><a class='icon up' href='" ).append (relative == null ? parent : relative + parent ).append (parent .endsWith ("/" ) ? "" : "/" ).append ("'>[..]</a></td><td>" );
163- builder .append (format .format ((resource .getLastModified () == null ? new Date (0L ) : resource .getLastModified ())))
206+ private static void appendFiles (List <Resource > files , StringBuilder builder , int i , String sortUrl ) {
207+ for (Resource entry : files ) {
208+ builder .append ("<tr class='" ).append ((++i & 1 ) == 1 ? "odd" : "even" ).append ("'><td><a class='icon file' href='" )
209+ .append (sortUrl ).append (entry .getName ()).append ("'>" )
210+ .append (entry .getName ()).append ("</a></td><td>" )
211+ .append (formatLastModified (entry .getLastModified ()))
212+ .append ("</td><td>" );
213+ formatSize (builder , entry .getContentLength ());
214+ builder .append ("</td></tr>\n " );
215+ }
216+ }
217+
218+ private static void appendDirectories (List <Resource > directories , StringBuilder builder , int i , String sortUrl ) {
219+ for (Resource entry : directories ) {
220+ builder .append ("<tr class='" ).append ((++i & 1 ) == 1 ? "odd" : "even" ).append ("'><td><a class='icon dir' href='" )
221+ .append (sortUrl ).append (entry .getName ()).append ("/'>" )
222+ .append (entry .getName ()).append ("</a></td><td>" )
223+ .append (formatLastModified (entry .getLastModified ()))
164224 .append ("</td><td>--</td></tr>\n " );
165225 }
226+ }
166227
228+ private static Comparator <Resource > getComparator (String sortColumn , String currentSortOrder ) {
229+ Comparator <Resource > comparator ;
230+ if ("lastModified" .equals (sortColumn )) {
231+ comparator = Comparator .comparing (
232+ entry -> (entry .getLastModified () == null ) ? new Date (0L ) : entry .getLastModified ()
233+ );
234+ } else {
235+ comparator = Comparator .comparing (Resource ::getName );
236+ }
237+
238+ if ("desc" .equals (currentSortOrder )) {
239+ comparator = comparator .reversed ();
240+ }
241+ return comparator ;
242+ }
243+
244+ private static void separateDirectoriesAndFiles (Resource resource , List <Resource > directories , List <Resource > files ) {
167245 for (Resource entry : resource .list ()) {
168- builder .append ("<tr class='" ).append ((++i & 1 ) == 1 ? "odd" : "even" ).append ("'><td><a class='icon " );
169- builder .append (entry .isDirectory () ? "dir" : "file" );
170- builder .append ("' href='" ).append (relative == null ? path : relative + path ).append (entry .getName ()).append (entry .isDirectory () ? "/" : "" ).append ("'>" ).append (entry .getName ()).append ("</a></td><td>" );
171- builder .append (format .format ((entry .getLastModified () == null ) ? new Date (0L ) : entry .getLastModified ()))
172- .append ("</td><td>" );
173246 if (entry .isDirectory ()) {
174- builder . append ( "--" );
247+ directories . add ( entry );
175248 } else {
176- formatSize ( builder , entry . getContentLength () );
249+ files . add ( entry );
177250 }
178- builder .append ("</td></tr>\n " );
179251 }
180- builder . append ( "</tbody> \n </table> \n </body> \n </html>" );
252+ }
181253
254+ private static StringBuilder buildDirectoryListingTable (String sortUrl , String sortColumn , String newSortOrder ) {
255+ StringBuilder builder = new StringBuilder ();
256+ builder .append ("<html>\n <head>\n <script src='" ).append (sortUrl ).append ("?js'></script>\n " )
257+ .append ("<link rel='stylesheet' type='text/css' href='" ).append (sortUrl ).append ("?css' />\n </head>\n " );
258+ builder .append ("<body onresize='growit()' onload='growit()'>\n <table id='thetable'>\n <thead>\n " );
259+ builder .append ("<tr><th class='loc' colspan='3'>Directory Listing - " ).append (sortUrl ).append ("</th></tr>\n " )
260+ .append ("<tr>" )
261+ .append ("<th class='label offset'><a href='" ).append (sortUrl ).append ("?sort=name&order=" ).append ("name" .equals (sortColumn ) ? newSortOrder : "asc" ).append ("'>Name</a></th>" )
262+ .append ("<th class='label'><a href='" ).append (sortUrl ).append ("?sort=lastModified&order=" ).append ("lastModified" .equals (sortColumn ) ? newSortOrder : "asc" ).append ("'>Last Modified</a></th>" )
263+ .append ("<th class='label'>Size</th></tr>\n </thead>\n " );
264+ builder .append ("<tfoot>\n <tr><th class=\" loc footer\" colspan=\" 3\" >Powered by Undertow</th></tr>\n </tfoot>\n <tbody>\n " );
182265 return builder ;
266+ }
183267
268+ private static String determineRelativePath (HttpServerExchange exchange , String path ) {
269+ String relative = null ;
270+ if (exchange != null ) {
271+ final Map <String , Object > context = exchange .getAttachment (Predicate .PREDICATE_CONTEXT );
272+ if (context != null ) {
273+ final PathPrefixMatchRecord trans = (PathPrefixMatchRecord ) context
274+ .get (PathPrefixPredicate .PREFIX_MATCH_RECORD );
275+ if (trans != null ) {
276+ if (trans .isOverWritten ()) {
277+ relative = trans .getPrefix ();
278+ if (!relative .endsWith ("/" ) && !path .startsWith ("/" )) {
279+ relative += "/" ;
280+ }
281+ }
282+ }
283+ }
284+ }
285+ return relative ;
184286 }
185287
186288 public static void renderDirectoryListing (HttpServerExchange exchange , Resource resource ) {
0 commit comments