/*
* Copyright 2013 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.artifacts.ivyservice.resolveengine.result;
import org.gradle.api.artifacts.component.ComponentSelector;
import org.gradle.api.artifacts.result.ResolutionResult;
import org.gradle.api.artifacts.result.ResolvedComponentResult;
import org.gradle.api.internal.artifacts.ImmutableModuleIdentifierFactory;
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.ComponentResult;
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.DependencyGraphComponent;
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.DependencyGraphEdge;
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.DependencyGraphNode;
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.DependencyGraphSelector;
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.DependencyGraphVisitor;
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.DependencyResult;
import org.gradle.api.internal.artifacts.result.DefaultResolutionResult;
import org.gradle.api.internal.cache.BinaryStore;
import org.gradle.api.internal.cache.Store;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.internal.Factory;
import org.gradle.internal.resolve.ModuleVersionResolveException;
import org.gradle.internal.serialize.Decoder;
import org.gradle.internal.serialize.Encoder;
import org.gradle.internal.time.Timer;
import org.gradle.internal.time.Timers;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.gradle.internal.UncheckedException.throwAsUncheckedException;
public class StreamingResolutionResultBuilder implements DependencyGraphVisitor {
private final static byte ROOT = 1;
private final static byte COMPONENT = 2;
private final static byte SELECTOR = 4;
private final static byte DEPENDENCY = 5;
private final Map<ComponentSelector, ModuleVersionResolveException> failures = new HashMap<ComponentSelector, ModuleVersionResolveException>();
private final BinaryStore store;
private final ComponentResultSerializer componentResultSerializer;
private final Store<ResolvedComponentResult> cache;
private final ComponentSelectorSerializer componentSelectorSerializer = new ComponentSelectorSerializer();
private final DependencyResultSerializer dependencyResultSerializer = new DependencyResultSerializer();
private final Set<Long> visitedComponents = new HashSet<Long>();
public StreamingResolutionResultBuilder(BinaryStore store, Store<ResolvedComponentResult> cache, ImmutableModuleIdentifierFactory moduleIdentifierFactory) {
this.componentResultSerializer = new ComponentResultSerializer(moduleIdentifierFactory);
this.store = store;
this.cache = cache;
}
public ResolutionResult complete() {
BinaryStore.BinaryData data = store.done();
RootFactory rootSource = new RootFactory(data, failures, cache, componentSelectorSerializer, dependencyResultSerializer, componentResultSerializer);
return new DefaultResolutionResult(rootSource);
}
@Override
public void start(final DependencyGraphNode root) {
}
@Override
public void finish(final DependencyGraphNode root) {
store.write(new BinaryStore.WriteAction() {
public void write(Encoder encoder) throws IOException {
encoder.writeByte(ROOT);
encoder.writeSmallLong(root.getOwner().getResultId());
}
});
}
@Override
public void visitNode(DependencyGraphNode node) {
final DependencyGraphComponent component = node.getOwner();
if (visitedComponents.add(component.getResultId())) {
store.write(new BinaryStore.WriteAction() {
public void write(Encoder encoder) throws IOException {
encoder.writeByte(COMPONENT);
componentResultSerializer.write(encoder, component);
}
});
}
}
@Override
public void visitSelector(final DependencyGraphSelector selector) {
store.write(new BinaryStore.WriteAction() {
@Override
public void write(Encoder encoder) throws IOException {
encoder.writeByte(SELECTOR);
encoder.writeSmallLong(selector.getResultId());
componentSelectorSerializer.write(encoder, selector.getRequested());
}
});
}
@Override
public void visitEdges(DependencyGraphNode node) {
final Long fromComponent = node.getOwner().getResultId();
final Set<? extends DependencyGraphEdge> dependencies = node.getOutgoingEdges();
if (!dependencies.isEmpty()) {
store.write(new BinaryStore.WriteAction() {
public void write(Encoder encoder) throws IOException {
encoder.writeByte(DEPENDENCY);
encoder.writeSmallLong(fromComponent);
encoder.writeSmallInt(dependencies.size());
for (DependencyGraphEdge dependency : dependencies) {
dependencyResultSerializer.write(encoder, dependency);
if (dependency.getFailure() != null) {
//by keying the failures only by 'requested' we lose some precision
//at edge case we'll lose info about a different exception if we have different failure for the same requested version
failures.put(dependency.getRequested(), dependency.getFailure());
}
}
}
});
}
}
private static class RootFactory implements Factory<ResolvedComponentResult> {
private final static Logger LOG = Logging.getLogger(RootFactory.class);
private final ComponentResultSerializer componentResultSerializer;
private final BinaryStore.BinaryData data;
private final Map<ComponentSelector, ModuleVersionResolveException> failures;
private final Store<ResolvedComponentResult> cache;
private final Object lock = new Object();
private final ComponentSelectorSerializer componentSelectorSerializer;
private final DependencyResultSerializer dependencyResultSerializer;
RootFactory(BinaryStore.BinaryData data, Map<ComponentSelector, ModuleVersionResolveException> failures, Store<ResolvedComponentResult> cache, ComponentSelectorSerializer componentSelectorSerializer, DependencyResultSerializer dependencyResultSerializer, ComponentResultSerializer componentResultSerializer) {
this.data = data;
this.failures = failures;
this.cache = cache;
this.componentResultSerializer = componentResultSerializer;
this.componentSelectorSerializer = componentSelectorSerializer;
this.dependencyResultSerializer = dependencyResultSerializer;
}
public ResolvedComponentResult create() {
synchronized (lock) {
return cache.load(new Factory<ResolvedComponentResult>() {
public ResolvedComponentResult create() {
try {
return data.read(new BinaryStore.ReadAction<ResolvedComponentResult>() {
public ResolvedComponentResult read(Decoder decoder) throws IOException {
return deserialize(decoder);
}
});
} finally {
try {
data.close();
} catch (IOException e) {
throw throwAsUncheckedException(e);
}
}
}
});
}
}
private ResolvedComponentResult deserialize(Decoder decoder) {
int valuesRead = 0;
byte type = -1;
Timer clock = Timers.startTimer();
try {
DefaultResolutionResultBuilder builder = new DefaultResolutionResultBuilder();
Map<Long, ComponentSelector> selectors = new HashMap<Long, ComponentSelector>();
while (true) {
type = decoder.readByte();
valuesRead++;
switch (type) {
case ROOT:
// Last entry, complete the result
Long rootId = decoder.readSmallLong();
ResolvedComponentResult root = builder.complete(rootId).getRoot();
LOG.debug("Loaded resolution results ({}) from {}", clock.getElapsed(), data);
return root;
case COMPONENT:
ComponentResult component = componentResultSerializer.read(decoder);
builder.visitComponent(component);
break;
case SELECTOR:
Long id = decoder.readSmallLong();
ComponentSelector selector = componentSelectorSerializer.read(decoder);
selectors.put(id, selector);
break;
case DEPENDENCY:
Long fromId = decoder.readSmallLong();
int size = decoder.readSmallInt();
List<DependencyResult> deps = new ArrayList<DependencyResult>(size);
for (int i = 0; i < size; i++) {
deps.add(dependencyResultSerializer.read(decoder, selectors, failures));
}
builder.visitOutgoingEdges(fromId, deps);
break;
default:
throw new IOException("Unknown value type read from stream: " + type);
}
}
} catch (IOException e) {
throw new RuntimeException("Problems loading the resolution results (" + clock.getElapsed() + "). "
+ "Read " + valuesRead + " values, last was: " + type, e);
}
}
}
}