1717
1818import java .awt .Desktop ;
1919import java .io .File ;
20+ import java .io .IOException ;
21+ import java .io .InputStream ;
22+ import java .net .MalformedURLException ;
2023import java .net .URL ;
2124import 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 ;
2229import 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 ;
2833import java .util .logging .Logger ;
2934import java .util .stream .Collectors ;
3035
36+ import org .springframework .core .io .Resource ;
37+ import org .springframework .core .io .support .PathMatchingResourcePatternResolver ;
38+ import org .springframework .core .io .support .ResourcePatternResolver ;
3139import org .utplsql .sqldev .dal .RealtimeReporterDao ;
3240import org .utplsql .sqldev .dal .UtplsqlDao ;
3341import org .utplsql .sqldev .exception .GenericDatabaseAccessException ;
4250
4351public class CodeCoverageReporter {
4452 private static final Logger logger = Logger .getLogger (CodeCoverageReporter .class .getName ());
53+ private static final String ASSETS_PATH = "coverage/assets/" ;
4554
4655 private String connectionName ;
4756 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 ;
5059 private CodeCoverageReporterDialog frame ;
5160 private String schemas ;
5261 private String includeObjects ;
5362 private String excludeObjects ;
63+ private Path assetDir ;
5464
5565 public CodeCoverageReporter (final List <String > pathList , final List <String > includeObjectList ,
5666 final String connectionName ) {
5767 this .pathList = pathList ;
5868 this .includeObjectList = includeObjectList ;
5969 setDefaultSchema ();
6070 setConnection (connectionName );
71+ setAssetDir ();
6172 }
6273
6374 // constructor for testing purposes only
@@ -67,6 +78,7 @@ public CodeCoverageReporter(final List<String> pathList, final List<String> incl
6778 this .includeObjectList = includeObjectList ;
6879 this .conn = conn ;
6980 setDefaultSchema ();
81+ setAssetDir ();
7082 }
7183
7284 private void setConnection (final String connectionName ) {
@@ -105,6 +117,59 @@ private void setDefaultSchema() {
105117 }
106118 }
107119
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+
108173 private ArrayList <String > toStringList (final String s ) {
109174 final ArrayList <String > list = new ArrayList <>();
110175 if (s != null && !s .isEmpty ()) {
@@ -142,7 +207,7 @@ private void run() {
142207
143208 private void runCodeCoverageWithRealtimeReporter () {
144209 final UtplsqlRunner runner = new UtplsqlRunner (pathList , toStringList (schemas ), toStringList (includeObjects ),
145- toStringList (excludeObjects ), connectionName );
210+ toStringList (excludeObjects ), getHtmlReportAssetPath (), connectionName );
146211 runner .runTestAsync ();
147212 }
148213
@@ -152,7 +217,7 @@ private void runCodeCoverageStandalone() {
152217 coverageConn = conn != null ? conn : DatabaseTools .cloneConnection (connectionName );
153218 final UtplsqlDao dao = new UtplsqlDao (coverageConn );
154219 final String html = dao .htmlCodeCoverage (pathList , toStringList (schemas ),
155- toStringList (includeObjects ), toStringList (excludeObjects ));
220+ toStringList (includeObjects ), toStringList (excludeObjects ), getHtmlReportAssetPath () );
156221 openInBrowser (html );
157222 } finally {
158223 try {
@@ -174,7 +239,7 @@ public static void openInBrowser(String html) {
174239 final URL url = file .toURI ().toURL ();
175240 logger .fine (() -> "Opening " + url .toExternalForm () + " in browser..." );
176241 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 )) {
178243 desktop .browse (url .toURI ());
179244 logger .fine (() -> url .toExternalForm () + " opened in browser." );
180245 } else {
@@ -229,9 +294,7 @@ public void setExcludeObjects(final String excludeObjects) {
229294 }
230295
231296 public Thread runAsync () {
232- final Thread thread = new Thread (() -> {
233- run ();
234- });
297+ final Thread thread = new Thread (this ::run );
235298 thread .setName ("code coverage reporter" );
236299 thread .start ();
237300 return thread ;
0 commit comments