/*
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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 org.gradle.api.internal.changedetection.state;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.hash.HashCode;
import org.gradle.api.internal.TaskInternal;
import org.gradle.api.internal.cache.StringInterner;
import org.gradle.api.internal.tasks.CacheableTaskOutputFilePropertySpec;
import org.gradle.api.internal.tasks.TaskOutputFilePropertySpec;
import org.gradle.cache.PersistentIndexedCache;
import org.gradle.internal.id.UniqueId;
import org.gradle.internal.scopeids.id.BuildScopeId;
import org.gradle.internal.serialize.AbstractSerializer;
import org.gradle.internal.serialize.Decoder;
import org.gradle.internal.serialize.Encoder;
import org.gradle.internal.serialize.Serializer;
import java.io.File;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class CacheBackedTaskHistoryRepository implements TaskHistoryRepository {
private static final int MAX_HISTORY_ENTRIES = 3;
private final FileSnapshotRepository snapshotRepository;
private final PersistentIndexedCache<String, ImmutableList<TaskExecutionSnapshot>> taskHistoryCache;
private final StringInterner stringInterner;
private final BuildScopeId buildScopeId;
public CacheBackedTaskHistoryRepository(TaskHistoryStore cacheAccess, FileSnapshotRepository snapshotRepository, StringInterner stringInterner, BuildScopeId buildScopeId) {
this.snapshotRepository = snapshotRepository;
this.stringInterner = stringInterner;
this.buildScopeId = buildScopeId;
TaskExecutionListSerializer serializer = new TaskExecutionListSerializer(stringInterner);
taskHistoryCache = cacheAccess.createCache("taskHistory", String.class, serializer, 10000, false);
}
public History getHistory(final TaskInternal task) {
final TaskExecutionList previousExecutions = loadPreviousExecutions(task);
final LazyTaskExecution currentExecution = new LazyTaskExecution(buildScopeId.getId());
currentExecution.snapshotRepository = snapshotRepository;
currentExecution.setOutputPropertyNamesForCacheKey(getOutputPropertyNamesForCacheKey(task));
currentExecution.setDeclaredOutputFilePaths(getDeclaredOutputFilePaths(task));
final LazyTaskExecution previousExecution = findBestMatchingPreviousExecution(currentExecution, previousExecutions.executions);
if (previousExecution != null) {
previousExecution.snapshotRepository = snapshotRepository;
}
return new History() {
public TaskExecution getPreviousExecution() {
return previousExecution;
}
public TaskExecution getCurrentExecution() {
return currentExecution;
}
public void update() {
previousExecutions.executions.addFirst(currentExecution);
if (currentExecution.inputFilesSnapshotIds == null && currentExecution.inputFilesSnapshot != null) {
ImmutableSortedMap.Builder<String, Long> builder = ImmutableSortedMap.naturalOrder();
for (Map.Entry<String, FileCollectionSnapshot> entry : currentExecution.inputFilesSnapshot.entrySet()) {
builder.put(entry.getKey(), snapshotRepository.add(entry.getValue()));
}
currentExecution.inputFilesSnapshotIds = builder.build();
}
if (currentExecution.outputFilesSnapshotIds == null && currentExecution.outputFilesSnapshot != null) {
ImmutableSortedMap.Builder<String, Long> builder = ImmutableSortedMap.naturalOrder();
for (Map.Entry<String, FileCollectionSnapshot> entry : currentExecution.outputFilesSnapshot.entrySet()) {
builder.put(entry.getKey(), snapshotRepository.add(entry.getValue()));
}
currentExecution.outputFilesSnapshotIds = builder.build();
}
if (currentExecution.discoveredFilesSnapshotId == null && currentExecution.discoveredFilesSnapshot != null) {
currentExecution.discoveredFilesSnapshotId = snapshotRepository.add(currentExecution.discoveredFilesSnapshot);
}
while (previousExecutions.executions.size() > MAX_HISTORY_ENTRIES) {
LazyTaskExecution execution = previousExecutions.executions.removeLast();
if (execution.inputFilesSnapshotIds != null) {
for (Long id : execution.inputFilesSnapshotIds.values()) {
snapshotRepository.remove(id);
}
}
if (execution.outputFilesSnapshotIds != null) {
for (Long id : execution.outputFilesSnapshotIds.values()) {
snapshotRepository.remove(id);
}
}
if (execution.discoveredFilesSnapshotId != null) {
snapshotRepository.remove(execution.discoveredFilesSnapshotId);
}
}
taskHistoryCache.put(task.getPath(), previousExecutions.snapshot());
}
};
}
private TaskExecutionList loadPreviousExecutions(final TaskInternal task) {
List<TaskExecutionSnapshot> history = taskHistoryCache.get(task.getPath());
TaskExecutionList result = new TaskExecutionList();
if (history != null) {
for (TaskExecutionSnapshot taskExecutionSnapshot : history) {
result.executions.add(new LazyTaskExecution(taskExecutionSnapshot));
}
}
return result;
}
private Iterable<String> getOutputPropertyNamesForCacheKey(TaskInternal task) {
// Find all output properties that go into the cache key
Iterable<TaskOutputFilePropertySpec> outputPropertiesForCacheKey =
Iterables.filter(task.getOutputs().getFileProperties(), new Predicate<TaskOutputFilePropertySpec>() {
@Override
public boolean apply(TaskOutputFilePropertySpec propertySpec) {
if (propertySpec instanceof CacheableTaskOutputFilePropertySpec) {
CacheableTaskOutputFilePropertySpec cacheablePropertySpec = (CacheableTaskOutputFilePropertySpec) propertySpec;
return cacheablePropertySpec.getOutputFile() != null;
}
return false;
}
});
// Extract the output property names
return Iterables.transform(outputPropertiesForCacheKey, new Function<TaskOutputFilePropertySpec, String>() {
@Override
public String apply(TaskOutputFilePropertySpec propertySpec) {
return propertySpec.getPropertyName();
}
});
}
private ImmutableSet<String> getDeclaredOutputFilePaths(TaskInternal task) {
ImmutableSet.Builder<String> declaredOutputFilePaths = ImmutableSortedSet.naturalOrder();
for (File file : task.getOutputs().getFiles()) {
declaredOutputFilePaths.add(stringInterner.intern(file.getAbsolutePath()));
}
return declaredOutputFilePaths.build();
}
private LazyTaskExecution findBestMatchingPreviousExecution(TaskExecution currentExecution, Collection<LazyTaskExecution> previousExecutions) {
Set<String> declaredOutputFilePaths = currentExecution.getDeclaredOutputFilePaths();
LazyTaskExecution bestMatch = null;
int bestMatchOverlap = 0;
for (LazyTaskExecution previousExecution : previousExecutions) {
Set<String> previousDeclaredOutputFilePaths = previousExecution.getDeclaredOutputFilePaths();
if (declaredOutputFilePaths.isEmpty() && previousDeclaredOutputFilePaths.isEmpty()) {
bestMatch = previousExecution;
break;
}
Set<String> intersection = Sets.intersection(declaredOutputFilePaths, previousDeclaredOutputFilePaths);
int overlap = intersection.size();
if (overlap > bestMatchOverlap) {
bestMatch = previousExecution;
bestMatchOverlap = overlap;
}
if (bestMatchOverlap == declaredOutputFilePaths.size()) {
break;
}
}
return bestMatch;
}
private static class TaskExecutionListSerializer extends AbstractSerializer<ImmutableList<TaskExecutionSnapshot>> {
private final LazyTaskExecution.TaskExecutionSnapshotSerializer executionSerializer;
private final StringInterner stringInterner;
TaskExecutionListSerializer(StringInterner stringInterner) {
this.stringInterner = stringInterner;
executionSerializer = new LazyTaskExecution.TaskExecutionSnapshotSerializer(this.stringInterner);
}
public ImmutableList<TaskExecutionSnapshot> read(Decoder decoder) throws Exception {
byte count = decoder.readByte();
List<TaskExecutionSnapshot> executions = new ArrayList<TaskExecutionSnapshot>(count);
for (int i = 0; i < count; i++) {
TaskExecutionSnapshot exec = executionSerializer.read(decoder);
executions.add(exec);
}
return ImmutableList.copyOf(executions);
}
public void write(Encoder encoder, ImmutableList<TaskExecutionSnapshot> value) throws Exception {
int size = value.size();
encoder.writeByte((byte) size);
for (TaskExecutionSnapshot execution : value) {
executionSerializer.write(encoder, execution);
}
}
}
private static class TaskExecutionList {
private final Deque<LazyTaskExecution> executions = new ArrayDeque<LazyTaskExecution>();
public String toString() {
return super.toString() + "[" + executions.size() + "]";
}
public ImmutableList<TaskExecutionSnapshot> snapshot() {
List<TaskExecutionSnapshot> snapshots = new ArrayList<TaskExecutionSnapshot>(executions.size());
for (LazyTaskExecution execution : executions) {
snapshots.add(execution.snapshot());
}
return ImmutableList.copyOf(snapshots);
}
}
private static class LazyTaskExecution extends TaskExecution {
private ImmutableSortedMap<String, Long> inputFilesSnapshotIds;
private ImmutableSortedMap<String, Long> outputFilesSnapshotIds;
private Long discoveredFilesSnapshotId;
private FileSnapshotRepository snapshotRepository;
private ImmutableSortedMap<String, FileCollectionSnapshot> inputFilesSnapshot;
private ImmutableSortedMap<String, FileCollectionSnapshot> outputFilesSnapshot;
private FileCollectionSnapshot discoveredFilesSnapshot;
/**
* Creates a mutable copy of the given snapshot.
*/
LazyTaskExecution(TaskExecutionSnapshot taskExecutionSnapshot) {
setBuildId(taskExecutionSnapshot.getBuildId());
setTaskImplementation(taskExecutionSnapshot.getTaskImplementation());
setTaskActionImplementations(taskExecutionSnapshot.getTaskActionsImplementations());
setInputProperties(taskExecutionSnapshot.getInputProperties());
setOutputPropertyNamesForCacheKey(taskExecutionSnapshot.getCacheableOutputProperties());
setDeclaredOutputFilePaths(taskExecutionSnapshot.getDeclaredOutputFilePaths());
inputFilesSnapshotIds = taskExecutionSnapshot.getInputFilesSnapshotIds();
outputFilesSnapshotIds = taskExecutionSnapshot.getOutputFilesSnapshotIds();
discoveredFilesSnapshotId = taskExecutionSnapshot.getDiscoveredFilesSnapshotId();
}
LazyTaskExecution(UniqueId buildId) {
setBuildId(buildId);
}
@Override
public ImmutableSortedMap<String, FileCollectionSnapshot> getInputFilesSnapshot() {
if (inputFilesSnapshot == null) {
ImmutableSortedMap.Builder<String, FileCollectionSnapshot> builder = ImmutableSortedMap.naturalOrder();
for (Map.Entry<String, Long> entry : inputFilesSnapshotIds.entrySet()) {
builder.put(entry.getKey(), snapshotRepository.get(entry.getValue()));
}
inputFilesSnapshot = builder.build();
}
return inputFilesSnapshot;
}
@Override
public void setInputFilesSnapshot(ImmutableSortedMap<String, FileCollectionSnapshot> inputFilesSnapshot) {
this.inputFilesSnapshot = inputFilesSnapshot;
this.inputFilesSnapshotIds = null;
}
@Override
public FileCollectionSnapshot getDiscoveredInputFilesSnapshot() {
if (discoveredFilesSnapshot == null) {
discoveredFilesSnapshot = snapshotRepository.get(discoveredFilesSnapshotId);
}
return discoveredFilesSnapshot;
}
@Override
public void setDiscoveredInputFilesSnapshot(FileCollectionSnapshot discoveredFilesSnapshot) {
this.discoveredFilesSnapshot = discoveredFilesSnapshot;
this.discoveredFilesSnapshotId = null;
}
@Override
public ImmutableSortedMap<String, FileCollectionSnapshot> getOutputFilesSnapshot() {
if (outputFilesSnapshot == null) {
ImmutableSortedMap.Builder<String, FileCollectionSnapshot> builder = ImmutableSortedMap.naturalOrder();
for (Map.Entry<String, Long> entry : outputFilesSnapshotIds.entrySet()) {
String propertyName = entry.getKey();
builder.put(propertyName, snapshotRepository.get(entry.getValue()));
}
outputFilesSnapshot = builder.build();
}
return outputFilesSnapshot;
}
@Override
public void setOutputFilesSnapshot(ImmutableSortedMap<String, FileCollectionSnapshot> outputFilesSnapshot) {
this.outputFilesSnapshot = outputFilesSnapshot;
outputFilesSnapshotIds = null;
}
public TaskExecutionSnapshot snapshot() {
return new TaskExecutionSnapshot(
getBuildId(),
getTaskImplementation(),
getTaskActionImplementations(),
getOutputPropertyNamesForCacheKey(),
getDeclaredOutputFilePaths(),
getInputProperties(),
inputFilesSnapshotIds,
discoveredFilesSnapshotId,
outputFilesSnapshotIds
);
}
static class TaskExecutionSnapshotSerializer implements Serializer<TaskExecutionSnapshot> {
private final InputPropertiesSerializer inputPropertiesSerializer;
private final StringInterner stringInterner;
TaskExecutionSnapshotSerializer(StringInterner stringInterner) {
this.inputPropertiesSerializer = new InputPropertiesSerializer();
this.stringInterner = stringInterner;
}
public TaskExecutionSnapshot read(Decoder decoder) throws Exception {
UniqueId buildId = UniqueId.from(decoder.readString());
ImmutableSortedMap<String, Long> inputFilesSnapshotIds = readSnapshotIds(decoder);
ImmutableSortedMap<String, Long> outputFilesSnapshotIds = readSnapshotIds(decoder);
Long discoveredFilesSnapshotId = decoder.readLong();
ImplementationSnapshot taskImplementation = readImplementation(decoder);
// We can't use an immutable list here because some hashes can be null
int taskActionsCount = decoder.readSmallInt();
ImmutableList.Builder<ImplementationSnapshot> taskActionImplementationsBuilder = ImmutableList.builder();
for (int j = 0; j < taskActionsCount; j++) {
ImplementationSnapshot actionImpl = readImplementation(decoder);
taskActionImplementationsBuilder.add(actionImpl);
}
ImmutableList<ImplementationSnapshot> taskActionImplementations = taskActionImplementationsBuilder.build();
int cacheableOutputPropertiesCount = decoder.readSmallInt();
ImmutableSortedSet.Builder<String> cacheableOutputPropertiesBuilder = ImmutableSortedSet.naturalOrder();
for (int j = 0; j < cacheableOutputPropertiesCount; j++) {
cacheableOutputPropertiesBuilder.add(decoder.readString());
}
ImmutableSortedSet<String> cacheableOutputProperties = cacheableOutputPropertiesBuilder.build();
int outputFilesCount = decoder.readSmallInt();
ImmutableSet.Builder<String> declaredOutputFilePathsBuilder = ImmutableSet.builder();
for (int j = 0; j < outputFilesCount; j++) {
declaredOutputFilePathsBuilder.add(stringInterner.intern(decoder.readString()));
}
ImmutableSet<String> declaredOutputFilePaths = declaredOutputFilePathsBuilder.build();
ImmutableSortedMap<String, ValueSnapshot> inputProperties = inputPropertiesSerializer.read(decoder);
return new TaskExecutionSnapshot(
buildId,
taskImplementation,
taskActionImplementations,
cacheableOutputProperties,
declaredOutputFilePaths,
inputProperties,
inputFilesSnapshotIds,
discoveredFilesSnapshotId,
outputFilesSnapshotIds
);
}
public void write(Encoder encoder, TaskExecutionSnapshot execution) throws Exception {
encoder.writeString(execution.getBuildId().asString());
writeSnapshotIds(encoder, execution.getInputFilesSnapshotIds());
writeSnapshotIds(encoder, execution.getOutputFilesSnapshotIds());
encoder.writeLong(execution.getDiscoveredFilesSnapshotId());
writeImplementation(encoder, execution.getTaskImplementation());
encoder.writeSmallInt(execution.getTaskActionsImplementations().size());
for (ImplementationSnapshot actionImpl : execution.getTaskActionsImplementations()) {
writeImplementation(encoder, actionImpl);
}
encoder.writeSmallInt(execution.getCacheableOutputProperties().size());
for (String outputFile : execution.getCacheableOutputProperties()) {
encoder.writeString(outputFile);
}
encoder.writeSmallInt(execution.getDeclaredOutputFilePaths().size());
for (String outputFile : execution.getDeclaredOutputFilePaths()) {
encoder.writeString(outputFile);
}
inputPropertiesSerializer.write(encoder, execution.getInputProperties());
}
private static ImplementationSnapshot readImplementation(Decoder decoder) throws IOException {
String typeName = decoder.readString();
HashCode classLoaderHash = decoder.readBoolean() ? HashCode.fromBytes(decoder.readBinary()) : null;
return new ImplementationSnapshot(typeName, classLoaderHash);
}
private static void writeImplementation(Encoder encoder, ImplementationSnapshot implementation) throws IOException {
encoder.writeString(implementation.getTypeName());
if (implementation.hasUnknownClassLoader()) {
encoder.writeBoolean(false);
} else {
encoder.writeBoolean(true);
encoder.writeBinary(implementation.getClassLoaderHash().asBytes());
}
}
private static ImmutableSortedMap<String, Long> readSnapshotIds(Decoder decoder) throws IOException {
int count = decoder.readSmallInt();
ImmutableSortedMap.Builder<String, Long> builder = ImmutableSortedMap.naturalOrder();
for (int snapshotIdx = 0; snapshotIdx < count; snapshotIdx++) {
String property = decoder.readString();
long id = decoder.readLong();
builder.put(property, id);
}
return builder.build();
}
private static void writeSnapshotIds(Encoder encoder, Map<String, Long> ids) throws IOException {
encoder.writeSmallInt(ids.size());
for (Map.Entry<String, Long> entry : ids.entrySet()) {
encoder.writeString(entry.getKey());
encoder.writeLong(entry.getValue());
}
}
}
}
}