• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

oracle / opengrok / #3642

23 Oct 2023 02:33PM UTC coverage: 75.784% (+1.4%) from 74.413%
#3642

push

web-flow
Sonar code smell issue fixes (#4450)

Signed-off-by: Gino Augustine <ginoaugustine@gmail.com>

200 of 200 new or added lines in 39 files covered. (100.0%)

44390 of 58574 relevant lines covered (75.78%)

0.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.96
/opengrok-web/src/main/java/org/opengrok/web/DirectoryListing.java
1
/*
2
 * CDDL HEADER START
3
 *
4
 * The contents of this file are subject to the terms of the
5
 * Common Development and Distribution License (the "License").
6
 * You may not use this file except in compliance with the License.
7
 *
8
 * See LICENSE.txt included in this distribution for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing Covered Code, include this CDDL HEADER in each
12
 * file and include the License file at LICENSE.txt.
13
 * If applicable, add the following below this CDDL HEADER, with the
14
 * fields enclosed by brackets "[]" replaced with your own identifying
15
 * information: Portions Copyright [yyyy] [name of copyright owner]
16
 *
17
 * CDDL HEADER END
18
 */
19

20
/*
21
 * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved.
22
 * Portions Copyright (c) 2011, Jens Elkner.
23
 * Portions Copyright (c) 2017, 2020, Chris Fraire <cfraire@me.com>.
24
 */
25
package org.opengrok.web;
26

27
import java.io.File;
28
import java.io.IOException;
29
import java.io.Writer;
30
import java.text.Format;
31
import java.text.SimpleDateFormat;
32
import java.util.ArrayList;
33
import java.util.Date;
34
import java.util.List;
35
import java.util.Locale;
36
import java.util.Objects;
37
import java.util.Optional;
38
import java.util.logging.Level;
39
import java.util.logging.Logger;
40

41
import org.jetbrains.annotations.Nullable;
42
import org.jetbrains.annotations.VisibleForTesting;
43
import org.opengrok.indexer.analysis.NullableNumLinesLOC;
44
import org.opengrok.indexer.configuration.PathAccepter;
45
import org.opengrok.indexer.configuration.RuntimeEnvironment;
46
import org.opengrok.indexer.history.CacheException;
47
import org.opengrok.indexer.history.HistoryGuru;
48
import org.opengrok.indexer.logger.LoggerFactory;
49
import org.opengrok.indexer.search.DirectoryEntry;
50
import org.opengrok.indexer.web.EftarFileReader;
51
import org.opengrok.indexer.web.Util;
52

53
/**
54
 * Generates HTML listing of a Directory.
55
 */
56
public class DirectoryListing {
57
    private static final Logger LOGGER = LoggerFactory.getLogger(DirectoryListing.class);
1✔
58
    protected static final String TD_END_TAG = "</td>";
59

60
    protected static final String BLANK_PLACEHOLDER = "-";
61
    private final EftarFileReader desc;
62
    private final long now;
63

64
    public DirectoryListing() {
1✔
65
        desc = null;
1✔
66
        now = System.currentTimeMillis();
1✔
67
    }
1✔
68

69
    public DirectoryListing(EftarFileReader desc) {
1✔
70
        this.desc = desc;
1✔
71
        now = System.currentTimeMillis();
1✔
72
    }
1✔
73

74
    /**
75
     * Write part of HTML code which contains file/directory last modification time and size.
76
     * The size printed for directories will be always {@link #BLANK_PLACEHOLDER}.
77
     * The time printed will be string representation of the non {@code null} time or {@link #BLANK_PLACEHOLDER}.
78
     * @param out write destination
79
     * @param file the file or directory to use for writing the data
80
     * @param date the time of the last commit that touched {@code file} or {@code null} if unknown
81
     * @param dateFormatter the formatter to use for pretty printing dates
82
     *
83
     * @throws IOException when writing to the {@code out} parameter failed
84
     */
85
    public void printDateSize(Writer out, File file, Date date, Format dateFormatter) throws IOException {
86
        out.write("<td>");
1✔
87
        if (date == null) {
1✔
88
            out.write(BLANK_PLACEHOLDER);
1✔
89
        } else {
90
            long lastModTime = date.getTime();
1✔
91
            if (now - lastModTime < 86400000) {
1✔
92
                out.write("Today");
1✔
93
            } else {
94
                out.write(dateFormatter.format(date));
1✔
95
            }
96
        }
97
        out.write("</td><td>");
1✔
98
        if (file.isDirectory()) {
1✔
99
            out.write(BLANK_PLACEHOLDER);
1✔
100
        } else {
101
            out.write(Util.readableSize(file.length()));
1✔
102
        }
103
        out.write(TD_END_TAG);
1✔
104
    }
1✔
105

106
    /**
107
     * Traverse directory until subdirectory with more than one item
108
     * (other than directory) or end of path is reached.
109
     * @param dir directory to traverse
110
     * @return string representing path with empty directories or the name of the directory
111
     */
112
    private static String getSimplifiedPath(File dir) {
113
        String[] files = dir.list();
1✔
114

115
        // Permissions can prevent getting list of items in the directory.
116
        if (files == null) {
1✔
117
            return dir.getName();
×
118
        }
119

120
        if (files.length == 1) {
1✔
121
            File entry = new File(dir, files[0]);
1✔
122
            PathAccepter pathAccepter = RuntimeEnvironment.getInstance().getPathAccepter();
1✔
123
            if (pathAccepter.accept(entry) && entry.isDirectory()) {
1✔
124
                return (dir.getName() + "/" + getSimplifiedPath(entry));
×
125
            }
126
        }
127

128
        return dir.getName();
1✔
129
    }
130

131
    /**
132
     * Calls
133
     * {@link #extraListTo(java.lang.String, java.io.File, java.io.Writer, java.lang.String, java.util.List)}
134
     * with {@code contextPath}, {@code dir}, {@code out}, {@code path},
135
     * and a list mapped from {@code files}.
136
     * @param contextPath context path
137
     * @param dir directory
138
     * @param out writer
139
     * @param path path
140
     * @param files list of files
141
     * @throws CacheException on error
142
     * @throws IOException I/O exception
143
     */
144
    @VisibleForTesting
145
    public void listTo(String contextPath, File dir, Writer out, String path, List<String> files)
146
            throws IOException, CacheException {
147

148
        List<DirectoryEntry> filesExtra = createDirectoryEntries(dir, path, files);
1✔
149
        extraListTo(contextPath, dir, out, path, filesExtra);
1✔
150
    }
1✔
151

152
    /**
153
     * @param dir the directory to list
154
     * @param path virtual path of the directory (usually the path name of
155
     *        <var>dir</var> with the source root directory stripped off).
156
     * @param files list of file paths
157
     * @return list of {@link DirectoryEntry} instances, filtered via {@link PathAccepter}
158
     * @throws CacheException if history cache operation failed
159
     */
160
    public List<DirectoryEntry> createDirectoryEntries(File dir, String path, List<String> files) throws CacheException {
161
        List<DirectoryEntry> entries = new ArrayList<>(files.size());
1✔
162

163
        for (String filePath : files) {
1✔
164
            File file = new File(dir, filePath);
1✔
165
            DirectoryEntry entry = new DirectoryEntry(file);
1✔
166
            entries.add(entry);
1✔
167
        }
1✔
168

169
        // Filter out entries that are not allowed.
170
        PathAccepter pathAccepter = RuntimeEnvironment.getInstance().getPathAccepter();
1✔
171
        entries.removeIf(entry -> !pathAccepter.accept(entry.getFile()));
1✔
172

173
        if (entries.isEmpty()) {
1✔
174
            return entries;
×
175
        }
176

177
        int offset = -1;
1✔
178
        EftarFileReader.FNode parentFNode = null;
1✔
179
        if (desc != null) {
1✔
180
            try {
181
                parentFNode = desc.getNode(path);
1✔
182
            } catch (IOException e) {
×
183
                LOGGER.log(Level.WARNING, String.format("cannot get Eftar node for path ''%s''", path), e);
×
184
            }
1✔
185
            if (parentFNode != null) {
1✔
186
                offset = parentFNode.getChildOffset();
1✔
187
            }
188
        }
189

190
        boolean fallback = HistoryGuru.getInstance().fillLastHistoryEntries(dir, entries);
1✔
191

192
        for (DirectoryEntry entry : entries) {
1✔
193
            File child = entry.getFile();
1✔
194
            String filename = child.getName();
1✔
195

196
            String pathDescription = "";
1✔
197
            try {
198
                if (offset > 0) {
1✔
199
                    pathDescription = desc.getChildTag(parentFNode, filename);
1✔
200
                }
201
            } catch (IOException e) {
×
202
                LOGGER.log(Level.WARNING, String.format("cannot get path description for '%s'", child), e);
×
203
            }
1✔
204
            entry.setPathDescription(pathDescription);
1✔
205

206
            if (entry.getDate() == null && fallback) {
1✔
207
                long date = child.lastModified();
1✔
208
                entry.setDate(new Date(date));
1✔
209
            }
210
        }
1✔
211

212
        return entries;
1✔
213
    }
214

215
    /**
216
     * Write HTML-ized listing of the given directory to the given destination.
217
     *
218
     * @param contextPath path used for link prefixes
219
     * @param dir the directory to list
220
     * @param out write destination
221
     * @param path virtual path of the directory (usually the path name of
222
     *  <var>dir</var> with the source root directory stripped off).
223
     * @param entries basenames of potential children of the directory to list,
224
     *  assuming that these were filtered by {@link PathAccepter}.
225
     * @throws IOException when cannot write to the {@code out} parameter
226
     */
227
    public void extraListTo(String contextPath, File dir, Writer out,
228
                                    String path, @Nullable List<DirectoryEntry> entries) throws IOException {
229
        // TODO this belongs to a jsp, not here
230
        var dateFormatter = new SimpleDateFormat("dd-MMM-yyyy", Locale.getDefault());
1✔
231

232
        out.write("<table id=\"dirlist\" class=\"tablesorter tablesorter-default\">\n");
1✔
233
        out.write("<thead>\n");
1✔
234
        out.write("<tr>\n");
1✔
235
        out.write("<th class=\"sorter-false\"></th>\n");
1✔
236
        out.write("<th>Name</th>\n");
1✔
237
        out.write("<th class=\"sorter-false\"></th>\n");
1✔
238
        out.write("<th class=\"sort-dates\">Date</th>\n");
1✔
239
        out.write("<th class=\"sort-groksizes\">Size</th>\n");
1✔
240
        out.write("<th>#Lines</th>\n");
1✔
241
        out.write("<th>LOC</th>\n");
1✔
242
        var strPathDescriptionsHeader = Optional.ofNullable(entries)
1✔
243
                .stream()
1✔
244
                .flatMap(List::stream)
1✔
245
                .map(DirectoryEntry::getPathDescription)
1✔
246
                .filter(Objects::nonNull)
1✔
247
                .filter(pathDescription -> !pathDescription.isEmpty())
1✔
248
                .findAny()
1✔
249
                .map(pathDescription -> "<th><samp>Description</samp></th>\n")
1✔
250
                .orElse("");
1✔
251
        out.write(strPathDescriptionsHeader);
1✔
252
        out.write("</tr>\n</thead>\n<tbody>\n");
1✔
253

254

255
        // Print the '..' entry even for empty directories.
256
        if (!path.isEmpty()) {
1✔
257
            out.write("<tr><td><p class=\"'r'\"/></td><td>");
1✔
258
            out.write("<b><a href=\"..\">..</a></b></td><td></td>");
1✔
259
            printDateSize(out, dir.getParentFile(), null, dateFormatter);
1✔
260
            out.write("</tr>\n");
1✔
261
        }
262

263
        if (entries != null) {
1✔
264
            for (DirectoryEntry entry : entries) {
1✔
265
                printDirectoryEntry(contextPath, out, path, entry, dateFormatter);
1✔
266
            }
1✔
267
        }
268
        out.write("</tbody>\n</table>");
1✔
269
    }
1✔
270
    private void printDirectoryEntry(String contextPath, Writer out,
271
                                       String path, DirectoryEntry entry,
272
                                     Format dateFormatter) throws IOException {
273
        File child = entry.getFile();
1✔
274
        String filename = child.getName();
1✔
275

276
        boolean isDir = child.isDirectory();
1✔
277

278
        out.write("<tr><td>");
1✔
279
        out.write("<p class=\"");
1✔
280
        out.write(isDir ? 'r' : 'p');
1✔
281
        out.write("\"/>");
1✔
282
        out.write("</td><td><a href=\"");
1✔
283
        if (isDir) {
1✔
284
            String longpath = getSimplifiedPath(child);
1✔
285
            out.write(Util.uriEncodePath(longpath));
1✔
286
            out.write("/\"><b>");
1✔
287
            int idx;
288
            if ((idx = longpath.lastIndexOf('/')) > 0) {
1✔
289
                out.write("<span class=\"simplified-path\">");
×
290
                out.write(longpath.substring(0, idx + 1));
×
291
                out.write("</span>");
×
292
                out.write(longpath.substring(idx + 1));
×
293
            } else {
294
                out.write(longpath);
1✔
295
            }
296
            out.write("</b></a>/");
1✔
297
        } else {
1✔
298
            out.write(Util.uriEncodePath(filename));
1✔
299
            out.write("\"");
1✔
300
            if (entry.getDescription() != null) {
1✔
301
                out.write(" class=\"title-tooltip\"");
1✔
302
                out.write(" title=\"");
1✔
303
                out.write(Util.encode(entry.getDescription()));
1✔
304
                out.write("\"");
1✔
305
            }
306
            out.write(">");
1✔
307
            out.write(filename);
1✔
308
            out.write("</a>");
1✔
309
        }
310
        out.write(TD_END_TAG);
1✔
311
        Util.writeHAD(out, contextPath, path + filename);
1✔
312
        printDateSize(out, child, entry.getDate(), dateFormatter);
1✔
313
        printNumlines(out, entry, isDir);
1✔
314
        printLoc(out, entry, isDir);
1✔
315
        var strPathDescription = Optional.ofNullable(entry.getPathDescription())
1✔
316
                .filter(pathDescription -> !pathDescription.isEmpty())
1✔
317
                .map(pathDescription -> "<td>" + pathDescription + TD_END_TAG)
1✔
318
                .orElse("");
1✔
319
        out.write(strPathDescription);
1✔
320
        out.write("</tr>\n");
1✔
321
    }
1✔
322
    private void printNumlines(Writer out, DirectoryEntry entry, boolean isDir)
323
            throws IOException {
324
        Long numlines = null;
1✔
325
        String readableNumlines = "";
1✔
326
        NullableNumLinesLOC extra = entry.getExtra();
1✔
327
        if (extra != null) {
1✔
328
            numlines = extra.getNumLines();
×
329
        }
330
        if (numlines != null) {
1✔
331
            readableNumlines = Util.readableCount(numlines, isDir);
×
332
        }
333

334
        out.write("<td class=\"numlines\">");
1✔
335
        out.write(readableNumlines);
1✔
336
        out.write(TD_END_TAG);
1✔
337
    }
1✔
338

339
    private void printLoc(Writer out, DirectoryEntry entry, boolean isDir)
340
            throws IOException {
341
        Long loc = null;
1✔
342
        String readableLoc = "";
1✔
343
        NullableNumLinesLOC extra = entry.getExtra();
1✔
344
        if (extra != null) {
1✔
345
            loc = extra.getLOC();
×
346
        }
347
        if (loc != null) {
1✔
348
            readableLoc = Util.readableCount(loc, isDir);
×
349
        }
350

351
        out.write("<td class=\"loc\">");
1✔
352
        out.write(readableLoc);
1✔
353
        out.write(TD_END_TAG);
1✔
354
    }
1✔
355
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc