/*
* Copyright (c) 2014, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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 com.google.dart.tools.debug.core.coverage;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.io.Files;
import com.google.dart.engine.utilities.source.SourceRange;
import com.google.dart.tools.core.DartCore;
import com.google.dart.tools.core.utilities.io.FileUtilities;
import com.google.dart.tools.debug.core.source.UriToFileResolver;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
public class CoverageManager {
private static final String MARKER_TEXT = "com.google.dart.tools.debug.core.coverageText";
private static final String MARKER_OVERVIEW = "com.google.dart.tools.debug.core.coverageOverview";
public static String createTempDir() {
return Files.createTempDir().getAbsolutePath();
}
@VisibleForTesting
public static TreeMap<Integer, Integer> parseHitMap(JSONObject coverageEntry) throws Exception {
TreeMap<Integer, Integer> hitMap = Maps.newTreeMap();
JSONArray hitsArray = coverageEntry.getJSONArray("hits");
for (int j = 0; j < hitsArray.length() / 2; j++) {
int line = hitsArray.getInt(2 * j + 0);
int hits = hitsArray.getInt(2 * j + 1);
Integer prevHits = hitMap.get(line);
if (prevHits != null) {
hits += prevHits.intValue();
}
hitMap.put(line, hits);
}
return hitMap;
}
public static void registerProcess(final UriToFileResolver uriToFileResolver,
final String tempDir, final String scriptPath, final Process process) {
Thread thread = new Thread("Coverage process handler") {
@Override
public void run() {
while (true) {
try {
process.waitFor();
break;
} catch (InterruptedException e) {
}
}
try {
parseCoverageDirectory(uriToFileResolver, tempDir, scriptPath);
} finally {
uriToFileResolver.dispose();
}
}
};
thread.setDaemon(true);
thread.start();
}
private static List<SourceRange> createMarkerRanges(final IFile file,
final TreeMap<Integer, Integer> hitMap) throws Exception {
List<SourceRange> markerRanges = Lists.newArrayList();
List<SourceRange> lineRanges = parseFileLines(file);
SourceRange markerRange = null;
for (Entry<Integer, Integer> entry : hitMap.entrySet()) {
if (entry.getValue() == 0) {
int line = entry.getKey();
if (line >= lineRanges.size()) {
continue;
}
SourceRange range = lineRanges.get(line - 1);
if (markerRange == null) {
markerRange = range;
} else {
markerRange = markerRange.getUnion(range);
}
} else if (markerRange != null) {
markerRanges.add(markerRange);
markerRange = null;
}
}
if (markerRange != null) {
markerRanges.add(markerRange);
}
return markerRanges;
}
private static void createMarkers(final Map<IFile, List<SourceRange>> filesMarkerRanges)
throws Exception {
final IWorkspace workspace = ResourcesPlugin.getWorkspace();
IWorkspaceRunnable runnable = new IWorkspaceRunnable() {
@Override
public void run(IProgressMonitor monitor) throws CoreException {
deleteMarkers();
for (Entry<IFile, List<SourceRange>> entry : filesMarkerRanges.entrySet()) {
IFile file = entry.getKey();
List<SourceRange> markerRanges = entry.getValue();
for (SourceRange range : markerRanges) {
createMarker(file, range, MARKER_TEXT);
createMarker(file, range, MARKER_OVERVIEW);
}
}
}
private void createMarker(IFile file, SourceRange range, String type) throws CoreException {
IMarker marker = file.createMarker(type);
marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_LOW);
marker.setAttribute(IMarker.CHAR_START, range.getOffset());
marker.setAttribute(IMarker.CHAR_END, range.getEnd());
}
private void deleteMarkers() throws CoreException {
IWorkspaceRoot root = workspace.getRoot();
root.deleteMarkers(MARKER_TEXT, true, IResource.DEPTH_INFINITE);
root.deleteMarkers(MARKER_OVERVIEW, true, IResource.DEPTH_INFINITE);
}
};
workspace.run(runnable, null, IWorkspace.AVOID_UPDATE, null);
}
private static IFile getFile(UriToFileResolver uriToFileResolver, IContainer container,
JSONObject coverageEntry) throws Exception {
String uri = coverageEntry.getString("source");
String path = uriToFileResolver.getFileForUri(uri);
if (path == null) {
return null;
}
File file = new File(path);
IFile resource = getResourceFile(file);
return resource;
}
private static IFile getResourceFile(File file) {
try {
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
URI uri = URI.create("file://" + file.getPath());
IFile[] files = root.findFilesForLocationURI(uri);
if (files.length == 1) {
return files[0];
}
} catch (Throwable e) {
}
return null;
}
private static IContainer getScriptContextContainer(String scriptPath) {
IResource scriptResource = ResourcesPlugin.getWorkspace().getRoot().findMember(scriptPath);
if (scriptResource == null) {
return null;
}
return scriptResource.getProject();
}
private static void parseCoverageDirectory(UriToFileResolver uriToFileResolver, String tempPath,
String scriptPath) {
IContainer contextContainer = getScriptContextContainer(scriptPath);
if (contextContainer == null) {
return;
}
// parse coverage output
final Map<IFile, List<SourceRange>> filesMarkerRanges = parseCoverageFilesInTempDirectory(
uriToFileResolver,
contextContainer,
tempPath);
if (filesMarkerRanges == null) {
return;
}
// add not loaded Dart files
try {
contextContainer.accept(new IResourceVisitor() {
@Override
public boolean visit(IResource resource) throws CoreException {
if (resource instanceof IFile) {
IFile file = (IFile) resource;
if (!DartCore.isDartLikeFile(file)) {
return false;
}
if (filesMarkerRanges.containsKey(file)) {
return false;
}
int length = (int) file.getLocation().toFile().length();
filesMarkerRanges.put(file, Lists.newArrayList(new SourceRange(0, length)));
}
return true;
}
});
} catch (Throwable e) {
e.printStackTrace();
}
// create markers for all files in the context
try {
createMarkers(filesMarkerRanges);
} catch (Throwable e) {
e.printStackTrace();
}
}
private static Map<IFile, List<SourceRange>> parseCoverageFile(
UriToFileResolver uriToFileResolver, IContainer container, File jsonFile) throws Exception {
Map<IFile, TreeMap<Integer, Integer>> filesHitMaps = Maps.newHashMap();
Map<IFile, List<SourceRange>> filesMarkerRanges = Maps.newHashMap();
String fileString = Files.toString(jsonFile, Charsets.UTF_8);
JSONObject rootObject = new JSONObject(fileString);
JSONArray coverageArray = rootObject.getJSONArray("coverage");
for (int i = 0; i < coverageArray.length(); i++) {
JSONObject coverageEntry = coverageArray.getJSONObject(i);
// prepare IFile
IFile file = getFile(uriToFileResolver, container, coverageEntry);
if (file == null) {
continue;
}
// prepare hit map
TreeMap<Integer, Integer> hitMap = parseHitMap(coverageEntry);
if (hitMap == null) {
continue;
}
// merge hit maps
TreeMap<Integer, Integer> hitMapOld = filesHitMaps.get(file);
if (hitMapOld != null) {
Set<Entry<Integer, Integer>> entrySet = hitMapOld.entrySet();
for (Entry<Integer, Integer> entry : entrySet) {
int line = entry.getKey();
int hitsOld = entry.getValue();
Integer hits = hitMap.get(line);
if (hits != null) {
hitMap.put(line, hitsOld + hits.intValue());
} else {
hitMap.put(line, hitsOld);
}
}
}
filesHitMaps.put(file, hitMap);
}
// add marker ranges
for (Entry<IFile, TreeMap<Integer, Integer>> entry : filesHitMaps.entrySet()) {
IFile file = entry.getKey();
TreeMap<Integer, Integer> hitMap = entry.getValue();
filesMarkerRanges.put(file, createMarkerRanges(file, hitMap));
}
//
return filesMarkerRanges;
}
private static Map<IFile, List<SourceRange>> parseCoverageFilesInTempDirectory(
UriToFileResolver uriToFileResolver, IContainer container, String tempPath) {
Map<IFile, List<SourceRange>> result = Maps.newHashMap();
File tempFile = new File(tempPath);
File[] outputFiles = tempFile.listFiles();
try {
for (File file : outputFiles) {
String fileName = file.getName().toLowerCase();
if (fileName.startsWith("dart-cov-") && fileName.endsWith(".json")) {
try {
Map<IFile, List<SourceRange>> fileResult = parseCoverageFile(
uriToFileResolver,
container,
file);
result.putAll(fileResult);
} catch (Exception e) {
}
}
}
} finally {
FileUtilities.delete(tempFile);
}
return result;
}
private static List<SourceRange> parseFileLines(IFile file) throws Exception {
List<SourceRange> lineRanges = Lists.newArrayList();
String contents = Files.toString(file.getLocation().toFile(), Charsets.UTF_8);
int start = 0;
int offset = 0;
while (offset < contents.length()) {
char c = contents.charAt(offset++);
if (c == '\r') {
continue;
}
if (c == '\n') {
int end = offset;
lineRanges.add(new SourceRange(start, end - start));
start = end;
}
}
return lineRanges;
}
}