17
17
18
18
import java .awt .Desktop ;
19
19
import java .io .File ;
20
+ import java .io .IOException ;
21
+ import java .io .InputStream ;
22
+ import java .net .MalformedURLException ;
20
23
import java .net .URL ;
21
24
import java .nio .charset .StandardCharsets ;
25
+ import java .nio .file .Files ;
26
+ import java .nio .file .Path ;
27
+ import java .nio .file .Paths ;
28
+ import java .nio .file .StandardCopyOption ;
22
29
import java .sql .Connection ;
23
- import java .util .ArrayList ;
24
- import java .util .Arrays ;
25
- import java .util .HashMap ;
26
- import java .util .List ;
27
- import java .util .Map ;
30
+ import java .util .*;
31
+ import java .util .jar .JarEntry ;
32
+ import java .util .jar .JarFile ;
28
33
import java .util .logging .Logger ;
29
34
import java .util .stream .Collectors ;
30
35
36
+ import org .springframework .core .io .Resource ;
37
+ import org .springframework .core .io .support .PathMatchingResourcePatternResolver ;
38
+ import org .springframework .core .io .support .ResourcePatternResolver ;
31
39
import org .utplsql .sqldev .dal .RealtimeReporterDao ;
32
40
import org .utplsql .sqldev .dal .UtplsqlDao ;
33
41
import org .utplsql .sqldev .exception .GenericDatabaseAccessException ;
42
50
43
51
public class CodeCoverageReporter {
44
52
private static final Logger logger = Logger .getLogger (CodeCoverageReporter .class .getName ());
53
+ private static final String ASSETS_PATH = "coverage/assets/" ;
45
54
46
55
private String connectionName ;
47
56
private Connection conn ;
48
- private List <String > pathList ;
49
- private List <String > includeObjectList ;
57
+ private final List <String > pathList ;
58
+ private final List <String > includeObjectList ;
50
59
private CodeCoverageReporterDialog frame ;
51
60
private String schemas ;
52
61
private String includeObjects ;
53
62
private String excludeObjects ;
63
+ private Path assetDir ;
54
64
55
65
public CodeCoverageReporter (final List <String > pathList , final List <String > includeObjectList ,
56
66
final String connectionName ) {
57
67
this .pathList = pathList ;
58
68
this .includeObjectList = includeObjectList ;
59
69
setDefaultSchema ();
60
70
setConnection (connectionName );
71
+ setAssetDir ();
61
72
}
62
73
63
74
// constructor for testing purposes only
@@ -67,6 +78,7 @@ public CodeCoverageReporter(final List<String> pathList, final List<String> incl
67
78
this .includeObjectList = includeObjectList ;
68
79
this .conn = conn ;
69
80
setDefaultSchema ();
81
+ setAssetDir ();
70
82
}
71
83
72
84
private void setConnection (final String connectionName ) {
@@ -105,6 +117,59 @@ private void setDefaultSchema() {
105
117
}
106
118
}
107
119
120
+ private void setAssetDir () {
121
+ try {
122
+ assetDir = Files .createTempDirectory ("utplsql_assets_" );
123
+ } catch (IOException e ) {
124
+ throw new GenericRuntimeException ("Cannot create temporary directory for code coverage report assets." , e );
125
+ }
126
+ populateCoverageAssets ();
127
+ }
128
+
129
+ // public for testing purposes only
130
+ public URL getHtmlReportAssetPath () {
131
+ try {
132
+ return Paths .get (assetDir .toString ()).toUri ().toURL ();
133
+ } catch (MalformedURLException e ) {
134
+ throw new GenericRuntimeException ("Cannot convert code coverage asset path to URL." , e );
135
+ }
136
+ }
137
+
138
+ private void copyStreamToFile (InputStream inputStream , Path file ) throws IOException {
139
+ file .toFile ().mkdirs ();
140
+ Files .copy (inputStream , file , StandardCopyOption .REPLACE_EXISTING );
141
+ }
142
+
143
+ private void populateCoverageAssets () {
144
+ logger .fine (() -> "Copying code coverage report assets to " + assetDir .toString () + "..." );
145
+ try {
146
+ final File file = new File (getClass ().getProtectionDomain ().getCodeSource ().getLocation ().getPath ());
147
+ if (file .isFile ()) {
148
+ // class loaded from a JAR file
149
+ final JarFile jar = new JarFile (file );
150
+ final List <JarEntry > entries = jar .stream ().filter (entry -> !entry .isDirectory () && entry .getName ().startsWith (ASSETS_PATH )).collect (Collectors .toList ());
151
+ for (JarEntry entry : entries ) {
152
+ Path f = Paths .get (assetDir .toString () + File .separator + entry .getName ().substring (ASSETS_PATH .length ()));
153
+ copyStreamToFile (jar .getInputStream (entry ), f );
154
+ }
155
+ } else {
156
+ // class loaded from file system (IDE or during test/build)
157
+ ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver ();
158
+ Resource [] resources = resolver .getResources (ResourcePatternResolver .CLASSPATH_ALL_URL_PREFIX + "/" + ASSETS_PATH + "**" );
159
+ for (Resource resource : resources ) {
160
+ if (Objects .requireNonNull (resource .getFilename ()).contains ("." )) {
161
+ // process files but not directories, assume that directories do not contain a period
162
+ String path = resource .getURL ().getPath ();
163
+ Path f = Paths .get (assetDir .toString () + File .separator + path .substring (path .lastIndexOf (ASSETS_PATH ) + ASSETS_PATH .length ()));
164
+ copyStreamToFile (resource .getInputStream (), f );
165
+ }
166
+ }
167
+ }
168
+ } catch (IOException e ) {
169
+ throw new GenericRuntimeException ("Error while copying coverage report assets to temporary directory." , e );
170
+ }
171
+ }
172
+
108
173
private ArrayList <String > toStringList (final String s ) {
109
174
final ArrayList <String > list = new ArrayList <>();
110
175
if (s != null && !s .isEmpty ()) {
@@ -142,7 +207,7 @@ private void run() {
142
207
143
208
private void runCodeCoverageWithRealtimeReporter () {
144
209
final UtplsqlRunner runner = new UtplsqlRunner (pathList , toStringList (schemas ), toStringList (includeObjects ),
145
- toStringList (excludeObjects ), connectionName );
210
+ toStringList (excludeObjects ), getHtmlReportAssetPath (), connectionName );
146
211
runner .runTestAsync ();
147
212
}
148
213
@@ -152,7 +217,7 @@ private void runCodeCoverageStandalone() {
152
217
coverageConn = conn != null ? conn : DatabaseTools .cloneConnection (connectionName );
153
218
final UtplsqlDao dao = new UtplsqlDao (coverageConn );
154
219
final String html = dao .htmlCodeCoverage (pathList , toStringList (schemas ),
155
- toStringList (includeObjects ), toStringList (excludeObjects ));
220
+ toStringList (includeObjects ), toStringList (excludeObjects ), getHtmlReportAssetPath () );
156
221
openInBrowser (html );
157
222
} finally {
158
223
try {
@@ -174,7 +239,7 @@ public static void openInBrowser(String html) {
174
239
final URL url = file .toURI ().toURL ();
175
240
logger .fine (() -> "Opening " + url .toExternalForm () + " in browser..." );
176
241
final Desktop desktop = Desktop .isDesktopSupported () ? Desktop .getDesktop () : null ;
177
- if (desktop != null && desktop .isSupported (Desktop .Action .BROWSE ) && url != null ) {
242
+ if (desktop != null && desktop .isSupported (Desktop .Action .BROWSE )) {
178
243
desktop .browse (url .toURI ());
179
244
logger .fine (() -> url .toExternalForm () + " opened in browser." );
180
245
} else {
@@ -229,9 +294,7 @@ public void setExcludeObjects(final String excludeObjects) {
229
294
}
230
295
231
296
public Thread runAsync () {
232
- final Thread thread = new Thread (() -> {
233
- run ();
234
- });
297
+ final Thread thread = new Thread (this ::run );
235
298
thread .setName ("code coverage reporter" );
236
299
thread .start ();
237
300
return thread ;
0 commit comments