blob: 468f3139f7eabba5232cca35971220a9e9ec4e20 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library analyzer.src.task.inputs;
import 'dart:collection';
import 'package:analyzer/task/model.dart';
/**
* A function that converts an object of the type [B] into a [TaskInput].
* This is used, for example, by a [ListToListTaskInput] to create task inputs
* for each value in a list of values.
*/
typedef TaskInput<E> GenerateTaskInputs<B, E>(B object);
/**
* A function that maps one [value] to another value.
*/
typedef R Mapper<P, R>(P value);
/**
* An input to an [AnalysisTask] that is computed by accessing a single result
* defined on a single target.
*/
class ConstantTaskInput<V> extends TaskInputImpl<V> {
final V value;
ConstantTaskInput(this.value);
@override
TaskInputBuilder<V> createBuilder() {
return new ConstantTaskInputBuilder<V>(this);
}
}
/**
* A [TaskInputBuilder] used to build an input based on a [ConstantTaskInput].
*/
class ConstantTaskInputBuilder<V> implements TaskInputBuilder<V> {
final ConstantTaskInput input;
ConstantTaskInputBuilder(this.input);
@override
ResultDescriptor get currentResult => null;
@override
AnalysisTarget get currentTarget => null;
@override
void set currentValue(Object value) {
throw new StateError('Only supported after moveNext() returns true');
}
@override
bool get flushOnAccess => false;
@override
V get inputValue => input.value;
@override
void currentValueNotAvailable() {
throw new StateError('Only supported after moveNext() returns true');
}
@override
bool moveNext() => false;
}
/**
* An input to an [AnalysisTask] that is computed by accessing a single result
* defined on a single target.
*/
class ListTaskInputImpl<E> extends SimpleTaskInput<List<E>>
with ListTaskInputMixin<E>
implements ListTaskInput<E> {
/**
* Initialize a newly created task input that computes the input by accessing
* the given [result] associated with the given [target].
*/
ListTaskInputImpl(AnalysisTarget target, ResultDescriptor<List<E>> result)
: super._unflushable(target, result);
}
/**
* A mixin-ready implementation of [ListTaskInput].
*/
abstract class ListTaskInputMixin<E> implements ListTaskInput<E> {
ListTaskInput /*<V>*/ toList(UnaryFunction<E, dynamic /*<V>*/ > mapper) {
return new ListToListTaskInput<E, dynamic /*V*/ >(this, mapper);
}
ListTaskInput /*<V>*/ toListOf(ResultDescriptor /*<V>*/ valueResult) {
return (this as ListTaskInputImpl<AnalysisTarget>).toList(valueResult.of);
}
MapTaskInput<E, dynamic /*V*/ > toMap(
UnaryFunction<E, dynamic /*<V>*/ > mapper) {
return new ListToMapTaskInput<E, dynamic /*V*/ >(this, mapper);
}
MapTaskInput<AnalysisTarget, dynamic /*V*/ > toMapOf(
ResultDescriptor /*<V>*/ valueResult) {
return (this as ListTaskInputImpl<AnalysisTarget>).toMap(valueResult.of);
}
}
/**
* An input to an [AnalysisTask] that is computed by the following steps. First
* another (base) task input is used to compute a [List]-valued result. An input
* generator function is then used to map each element of that list to a task
* input. Finally, each of the task inputs are used to access analysis results,
* and the list of the analysis results is used as the input to the task.
*/
class ListToListTaskInput<B, E>
extends _ListToCollectionTaskInput<B, E, List<E>>
with ListTaskInputMixin<E> {
/**
* Initialize a result accessor to use the given [baseAccessor] to access a
* list of values that can be passed to the given [generateTaskInputs] to
* generate a list of task inputs that can be used to access the elements of
* the input being accessed.
*/
ListToListTaskInput(TaskInput<List<B>> baseAccessor,
GenerateTaskInputs<B, E> generateTaskInputs)
: super(baseAccessor, generateTaskInputs);
@override
TaskInputBuilder<List<E>> createBuilder() =>
new ListToListTaskInputBuilder<B, E>(this);
}
/**
* A [TaskInputBuilder] used to build an input based on a [ListToListTaskInput].
*/
class ListToListTaskInputBuilder<B, E>
extends _ListToCollectionTaskInputBuilder<B, E, List<E>> {
/**
* The list of values being built.
*/
List<E> _resultValue;
/**
* Initialize a newly created task input builder that computes the result
* specified by the given [input].
*/
ListToListTaskInputBuilder(ListToListTaskInput<B, E> input) : super(input);
@override
void _addResultElement(B baseElement, E resultElement) {
_resultValue.add(resultElement);
}
@override
void _initResultValue() {
_resultValue = <E>[];
}
}
/**
* An input to an [AnalysisTask] that is computed by the following steps. First
* another (base) task input is used to compute a [List]-valued result. An input
* generator function is then used to map each element of that list to a task
* input. Finally, each of the task inputs are used to access analysis results,
* and the map of the base elements to the analysis results is used as the
* input to the task.
*/
class ListToMapTaskInput<B, E>
extends _ListToCollectionTaskInput<B, E, Map<B, E>>
with MapTaskInputMixin<B, E> {
/**
* Initialize a result accessor to use the given [baseAccessor] to access a
* list of values that can be passed to the given [generateTaskInputs] to
* generate a list of task inputs that can be used to access the elements of
* the input being accessed.
*/
ListToMapTaskInput(TaskInput<List<B>> baseAccessor,
GenerateTaskInputs<B, E> generateTaskInputs)
: super(baseAccessor, generateTaskInputs);
@override
TaskInputBuilder<Map<B, E>> createBuilder() =>
new ListToMapTaskInputBuilder<B, E>(this);
}
/**
* A [TaskInputBuilder] used to build an input based on a [ListToMapTaskInput].
*/
class ListToMapTaskInputBuilder<B, E>
extends _ListToCollectionTaskInputBuilder<B, E, Map<B, E>> {
/**
* The map being built.
*/
Map<B, E> _resultValue;
/**
* Initialize a newly created task input builder that computes the result
* specified by the given [input].
*/
ListToMapTaskInputBuilder(ListToMapTaskInput<B, E> input) : super(input);
@override
void _addResultElement(B baseElement, E resultElement) {
_resultValue[baseElement] = resultElement;
}
@override
void _initResultValue() {
_resultValue = new HashMap<B, E>();
}
}
/**
* A mixin-ready implementation of [MapTaskInput].
*/
abstract class MapTaskInputMixin<K, V> implements MapTaskInput<K, V> {
TaskInput<List /*<E>*/ > toFlattenList(
BinaryFunction<K, dynamic /*element of V*/, dynamic /*<E>*/ > mapper) {
return new MapToFlattenListTaskInput<K, dynamic /*element of V*/,
dynamic /*E*/ >(
this as MapTaskInput<K, List /*<element of V>*/ >, mapper);
}
}
/**
* A [TaskInput] that is computed by the following steps.
*
* First the [base] task input is used to compute a [Map]-valued result.
* The values of the [Map] must be [List]s.
*
* The given [mapper] is used to transform each key / value pair of the [Map]
* into task inputs.
*
* Finally, each of the task inputs are used to access analysis results,
* and the list of the results is used as the input.
*/
class MapToFlattenListTaskInput<K, V, E> extends TaskInputImpl<List<E>> {
final MapTaskInput<K, List<V>> base;
final BinaryFunction<K, V, E> mapper;
MapToFlattenListTaskInput(this.base, this.mapper);
@override
TaskInputBuilder<List<E>> createBuilder() {
return new MapToFlattenListTaskInputBuilder<K, V, E>(base, mapper);
}
}
/**
* The [TaskInputBuilder] for [MapToFlattenListTaskInput].
*/
class MapToFlattenListTaskInputBuilder<K, V, E>
implements TaskInputBuilder<List<E>> {
final MapTaskInput<K, List<V>> base;
final BinaryFunction<K, V, E> mapper;
TaskInputBuilder currentBuilder;
Map<K, List<V>> baseMap;
Iterator<K> keyIterator;
Iterator<V> valueIterator;
final List<E> inputValue = <E>[];
MapToFlattenListTaskInputBuilder(this.base, this.mapper) {
currentBuilder = base.createBuilder();
}
@override
ResultDescriptor get currentResult {
if (currentBuilder == null) {
return null;
}
return currentBuilder.currentResult;
}
AnalysisTarget get currentTarget {
if (currentBuilder == null) {
return null;
}
return currentBuilder.currentTarget;
}
@override
void set currentValue(Object value) {
if (currentBuilder == null) {
throw new StateError(
'Cannot set the result value when there is no current result');
}
currentBuilder.currentValue = value;
}
@override
bool get flushOnAccess => currentBuilder.flushOnAccess;
@override
void currentValueNotAvailable() {
if (currentBuilder == null) {
throw new StateError(
'Cannot set the result value when there is no current result');
}
currentBuilder.currentValueNotAvailable();
}
@override
bool moveNext() {
// Prepare base Map.
if (baseMap == null) {
if (currentBuilder.moveNext()) {
return true;
}
baseMap = currentBuilder.inputValue;
if (baseMap == null) {
// No base map could be computed due to a circular dependency. Use an
// empty map so that no further results will be computed.
baseMap = {};
}
keyIterator = baseMap.keys.iterator;
// Done with this builder.
currentBuilder = null;
}
// Prepare the next result value.
if (currentBuilder != null) {
if (currentBuilder.moveNext()) {
return true;
}
// Add the result value for the current Map key/value.
E resultValue = currentBuilder.inputValue;
if (resultValue != null) {
inputValue.add(resultValue);
}
// Done with this builder.
currentBuilder = null;
}
// Move to the next Map value.
if (valueIterator != null && valueIterator.moveNext()) {
K key = keyIterator.current;
V value = valueIterator.current;
currentBuilder = mapper(key, value).createBuilder();
return moveNext();
}
// Move to the next Map key.
if (keyIterator.moveNext()) {
K key = keyIterator.current;
valueIterator = baseMap[key].iterator;
return moveNext();
}
// No more Map values/keys to transform.
return false;
}
}
/**
* An input to an [AnalysisTask] that is computed by mapping the value of
* another task input to a list of values.
*/
class ObjectToListTaskInput<E> extends TaskInputImpl<List<E>>
with ListTaskInputMixin<E>
implements ListTaskInput<E> {
/**
* The input used to compute the value to be mapped.
*/
final TaskInput baseInput;
/**
* The function used to map the value of the base input to the list of values.
*/
final Mapper<Object, List<E>> mapper;
/**
* Initialize a newly created task input that computes the input by accessing
* the given [result] associated with the given [target].
*/
ObjectToListTaskInput(this.baseInput, this.mapper);
@override
TaskInputBuilder<List<E>> createBuilder() =>
new ObjectToListTaskInputBuilder<E>(this);
@override
ListTaskInput /*<V>*/ toListOf(ResultDescriptor /*<V>*/ valueResult) {
return new ListToListTaskInput<E, dynamic /*V*/ >(
this, valueResult.of as dynamic);
}
@override
MapTaskInput<AnalysisTarget, dynamic /*V*/ > toMapOf(
ResultDescriptor /*<V>*/ valueResult) {
return new ListToMapTaskInput<AnalysisTarget, dynamic /*V*/ >(
this as dynamic, valueResult.of);
}
}
/**
* A [TaskInputBuilder] used to build an input based on a [SimpleTaskInput].
*/
class ObjectToListTaskInputBuilder<E> implements TaskInputBuilder<List<E>> {
/**
* The input being built.
*/
final ObjectToListTaskInput<E> input;
/**
* The builder created by the input.
*/
TaskInputBuilder builder;
/**
* The value of the input being built, or `null` if the value hasn't been set
* yet or if no result is available ([currentValueNotAvailable] was called).
*/
List<E> _inputValue = null;
/**
* Initialize a newly created task input builder that computes the result
* specified by the given [input].
*/
ObjectToListTaskInputBuilder(this.input) {
builder = input.baseInput.createBuilder();
}
@override
ResultDescriptor get currentResult {
if (builder == null) {
return null;
}
return builder.currentResult;
}
@override
AnalysisTarget get currentTarget {
if (builder == null) {
return null;
}
return builder.currentTarget;
}
@override
void set currentValue(Object value) {
if (builder == null) {
throw new StateError(
'Cannot set the result value when there is no current result');
}
builder.currentValue = value;
}
@override
bool get flushOnAccess => builder.flushOnAccess;
@override
List<E> get inputValue {
if (builder != null) {
throw new StateError('Result value has not been created');
}
return _inputValue;
}
@override
void currentValueNotAvailable() {
if (builder == null) {
throw new StateError(
'Cannot set the result value when there is no current result');
}
builder.currentValueNotAvailable();
}
@override
bool moveNext() {
if (builder == null) {
return false;
} else if (builder.moveNext()) {
return true;
} else {
// This might not be the right semantics. If the value could not be
// computed then we pass the resulting `null` in to the mapper function.
// Unfortunately, we cannot tell the difference between a `null` that's
// there because no value could be computed and a `null` that's there
// because that's what *was* computed.
_inputValue = input.mapper(builder.inputValue);
builder = null;
return false;
}
}
}
/**
* An input to an [AnalysisTask] that is computed by accessing a single result
* defined on a single target.
*/
class SimpleTaskInput<V> extends TaskInputImpl<V> {
/**
* The target on which the result is defined.
*/
final AnalysisTarget target;
/**
* The result to be accessed.
*/
final ResultDescriptor<V> result;
/**
* Return `true` if the value accessed by this input builder should be flushed
* from the cache at the time it is retrieved.
*/
final bool flushOnAccess;
/**
* Initialize a newly created task input that computes the input by accessing
* the given [result] associated with the given [target].
*/
SimpleTaskInput(this.target, this.result, {this.flushOnAccess: false});
/**
* Initialize a newly created task input that computes the input by accessing
* the given [result] associated with the given [target].
*/
SimpleTaskInput._unflushable(this.target, this.result)
: flushOnAccess = false;
@override
TaskInputBuilder<V> createBuilder() => new SimpleTaskInputBuilder<V>(this);
}
/**
* A [TaskInputBuilder] used to build an input based on a [SimpleTaskInput].
*/
class SimpleTaskInputBuilder<V> implements TaskInputBuilder<V> {
/**
* The state value indicating that the builder is positioned before the single
* result.
*/
static const _BEFORE = -1;
/**
* The state value indicating that the builder is positioned at the single
* result.
*/
static const _AT = 0;
/**
* The state value indicating that the builder is positioned after the single
* result.
*/
static const _AFTER = 1;
/**
* The input being built.
*/
final SimpleTaskInput<V> input;
/**
* The value of the input being built. `null` if the value hasn't been set
* yet, or if no result is available ([currentValueNotAvailable] was called).
*/
V _resultValue = null;
/**
* The state of the builder.
*/
int _state = _BEFORE;
/**
* A flag indicating whether the result value was explicitly set.
*/
bool _resultSet = false;
/**
* Initialize a newly created task input builder that computes the result
* specified by the given [input].
*/
SimpleTaskInputBuilder(this.input);
@override
ResultDescriptor get currentResult => _state == _AT ? input.result : null;
@override
AnalysisTarget get currentTarget => _state == _AT ? input.target : null;
@override
void set currentValue(Object value) {
if (_state != _AT) {
throw new StateError(
'Cannot set the result value when there is no current result');
}
_resultValue = value as V;
_resultSet = true;
}
@override
bool get flushOnAccess => input.flushOnAccess;
@override
V get inputValue {
if (_state != _AFTER) {
throw new StateError('Result value has not been created');
}
return _resultValue;
}
@override
void currentValueNotAvailable() {
if (_state != _AT) {
throw new StateError(
'Cannot set the result value when there is no current result');
}
_resultValue = null;
_resultSet = true;
}
@override
bool moveNext() {
if (_state == _BEFORE) {
_state = _AT;
return true;
} else {
if (!_resultSet) {
throw new StateError(
'The value of the current result must be set before moving to the next result.');
}
_state = _AFTER;
return false;
}
}
}
abstract class TaskInputImpl<V> implements TaskInput<V> {
@override
ListTaskInput /*<E>*/ mappedToList(List /*<E>*/ mapper(V value)) {
return new ObjectToListTaskInput(this, mapper);
}
}
/**
* A [TaskInputBuilder] used to build an input based on one or more other task
* inputs. The task inputs to be built are specified by a table mapping the name
* of the input to the task used to access the input's value.
*/
class TopLevelTaskInputBuilder
implements TaskInputBuilder<Map<String, Object>> {
/**
* The descriptors describing the inputs to be built.
*/
final Map<String, TaskInput> inputDescriptors;
/**
* The names of the inputs. There are the keys from the [inputDescriptors] in
* an indexable form.
*/
List<String> inputNames;
/**
* The index of the input name associated with the current result and target.
*/
int nameIndex = -1;
/**
* The builder used to build the current result.
*/
TaskInputBuilder currentBuilder;
/**
* The inputs that are being or have been built. The map will be incomplete
* unless the method [moveNext] returns `false`.
*/
final Map<String, Object> inputs = new HashMap<String, Object>();
/**
* Initialize a newly created task input builder to build the inputs described
* by the given [inputDescriptors].
*/
TopLevelTaskInputBuilder(this.inputDescriptors) {
inputNames = inputDescriptors.keys.toList();
}
@override
ResultDescriptor get currentResult {
if (currentBuilder == null) {
return null;
}
return currentBuilder.currentResult;
}
@override
AnalysisTarget get currentTarget {
if (currentBuilder == null) {
return null;
}
return currentBuilder.currentTarget;
}
@override
void set currentValue(Object value) {
if (currentBuilder == null) {
throw new StateError(
'Cannot set the result value when there is no current result');
}
currentBuilder.currentValue = value;
}
@override
bool get flushOnAccess => currentBuilder.flushOnAccess;
@override
Map<String, Object> get inputValue {
if (nameIndex < inputNames.length) {
throw new StateError('Result value has not been created');
}
return inputs;
}
/**
* Assuming that there is a current input, return its name.
*/
String get _currentName => inputNames[nameIndex];
@override
void currentValueNotAvailable() {
if (currentBuilder == null) {
throw new StateError(
'Cannot set the result value when there is no current result');
}
currentBuilder.currentValueNotAvailable();
}
@override
bool moveNext() {
if (nameIndex >= inputNames.length) {
// We have already computed all of the results, so just return false.
return false;
}
if (nameIndex < 0) {
// This is the first time moveNext has been invoked, so we just determine
// whether there are any results to be computed.
nameIndex = 0;
} else {
if (currentBuilder.moveNext()) {
// We are still working on building the value associated with the
// current name.
return true;
}
if (currentBuilder.inputValue != null) {
inputs[_currentName] = currentBuilder.inputValue;
}
nameIndex++;
}
if (nameIndex >= inputNames.length) {
// There is no next value, so we're done.
return false;
}
currentBuilder = inputDescriptors[_currentName].createBuilder();
while (!currentBuilder.moveNext()) {
if (currentBuilder.inputValue != null) {
inputs[_currentName] = currentBuilder.inputValue;
}
nameIndex++;
if (nameIndex >= inputNames.length) {
// There is no next value, so we're done.
return false;
}
currentBuilder = inputDescriptors[_currentName].createBuilder();
}
return true;
}
}
/**
* An input to an [AnalysisTask] that is computed by the following steps. First
* another (base) task input is used to compute a [List]-valued result. An input
* generator function is then used to map each element of that list to a task
* input. Finally, each of the task inputs are used to access analysis results,
* and a collection of the analysis results is used as the input to the task.
*/
abstract class _ListToCollectionTaskInput<B, E, C> extends TaskInputImpl<C> {
/**
* The accessor used to access the list of elements being mapped.
*/
final TaskInput<List<B>> baseAccessor;
/**
* The function used to convert an element in the list returned by the
* [baseAccessor] to a task input.
*/
final GenerateTaskInputs<B, E> generateTaskInputs;
/**
* Initialize a result accessor to use the given [baseAccessor] to access a
* list of values that can be passed to the given [generateTaskInputs] to
* generate a list of task inputs that can be used to access the elements of
* the input being accessed.
*/
_ListToCollectionTaskInput(this.baseAccessor, this.generateTaskInputs);
}
/**
* A [TaskInputBuilder] used to build an [_ListToCollectionTaskInput].
*/
abstract class _ListToCollectionTaskInputBuilder<B, E, C>
implements TaskInputBuilder<C> {
/**
* The input being built.
*/
final _ListToCollectionTaskInput<B, E, C> input;
/**
* The builder used to build the current result.
*/
TaskInputBuilder currentBuilder;
/**
* The list of values computed by the [input]'s base accessor.
*/
List<B> _baseList = null;
/**
* The index in the [_baseList] of the value for which a value is currently
* being built.
*/
int _baseListIndex = -1;
/**
* The element of the [_baseList] for which a value is currently being built.
*/
B _baseListElement;
/**
* Initialize a newly created task input builder that computes the result
* specified by the given [input].
*/
_ListToCollectionTaskInputBuilder(this.input);
@override
ResultDescriptor get currentResult {
if (currentBuilder == null) {
return null;
}
return currentBuilder.currentResult;
}
@override
AnalysisTarget get currentTarget {
if (currentBuilder == null) {
return null;
}
return currentBuilder.currentTarget;
}
@override
void set currentValue(Object value) {
if (currentBuilder == null) {
throw new StateError(
'Cannot set the result value when there is no current result');
}
currentBuilder.currentValue = value;
}
@override
bool get flushOnAccess => currentBuilder.flushOnAccess;
@override
C get inputValue {
if (currentBuilder != null || _resultValue == null) {
throw new StateError('Result value has not been created');
}
return _resultValue;
}
/**
* The list of values being built.
*/
C get _resultValue;
@override
void currentValueNotAvailable() {
if (currentBuilder == null) {
throw new StateError(
'Cannot set the result value when there is no current result');
}
currentBuilder.currentValueNotAvailable();
}
@override
bool moveNext() {
if (currentBuilder == null) {
if (_resultValue == null) {
// This is the first time moveNext has been invoked, so start by
// computing the list of values from which the results will be derived.
currentBuilder = input.baseAccessor.createBuilder();
return currentBuilder.moveNext();
} else {
// We have already computed all of the results, so just return false.
return false;
}
}
if (currentBuilder.moveNext()) {
return true;
}
if (_resultValue == null) {
// We have finished computing the list of values from which the results
// will be derived.
_baseList = currentBuilder.inputValue;
if (_baseList == null) {
// No base list could be computed due to a circular dependency. Use an
// empty list so that no further results will be computed.
_baseList = [];
}
_baseListIndex = 0;
_initResultValue();
} else {
// We have finished computing one of the elements in the result list.
if (currentBuilder.inputValue != null) {
_addResultElement(_baseListElement, currentBuilder.inputValue);
}
_baseListIndex++;
}
if (_baseListIndex >= _baseList.length) {
currentBuilder = null;
return false;
}
_baseListElement = _baseList[_baseListIndex];
currentBuilder = input.generateTaskInputs(_baseListElement).createBuilder();
return currentBuilder.moveNext();
}
void _addResultElement(B baseElement, E resultElement);
void _initResultValue();
}