Generator/Sources/NeedleFramework/Generating/Pluginized/PluginizedDependencyProviderContentTask.swift (114 lines of code) (raw):
//
// Copyright (c) 2018. Uber Technologies
//
// 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.
//
import Concurrency
import Foundation
import SourceParsingFramework
/// The task that walks through the chain of parents as well as auxillary
/// providers for each dependency item of the dependency protocol that this
/// provider class needs to satisfy.
class PluginizedDependencyProviderContentTask: AbstractTask<[PluginizedProcessedDependencyProvider]> {
/// Initializer.
///
/// - parameter providers: The list of providers that we need to fill in.
/// - parameter pluginizedComponents: The list of pluginized components
/// to check for auzillary properties.
init(providers: [DependencyProvider], pluginizedComponents: [PluginizedComponent]) {
self.providers = providers
nonCoreComponentNames = Set(pluginizedComponents.map { pluginizedComponent in
pluginizedComponent.nonCoreComponent.name
})
var nonCoreComponentMap = [String: AuxillaryProperties]()
var pluginExtensionMap = [String: AuxillaryProperties]()
var auxilarySourceParentDependency = [String: String]()
for pluginizedComponent in pluginizedComponents {
nonCoreComponentMap[pluginizedComponent.data.name] = AuxillaryProperties(sourceName: pluginizedComponent.nonCoreComponent.name, properties: pluginizedComponent.nonCoreComponent.properties)
auxilarySourceParentDependency[pluginizedComponent.nonCoreComponent.name] = pluginizedComponent.nonCoreComponent.dependency.name
pluginExtensionMap[pluginizedComponent.data.name] = AuxillaryProperties(sourceName: pluginizedComponent.pluginExtension.name, properties:pluginizedComponent.pluginExtension.properties)
auxilarySourceParentDependency[pluginizedComponent.pluginExtension.name] = pluginizedComponent.data.dependency.name
}
self.nonCoreComponentMap = nonCoreComponentMap
self.pluginExtensionMap = pluginExtensionMap
self.auxilarySourceParentDependency = auxilarySourceParentDependency
super.init(id: TaskIds.pluginizedDependencyProviderContentTask.rawValue)
}
/// Execute the task and returns the processed in-memory dependency graph
/// data models.
///
/// - returns: The list of `ProcessedDependencyProvider`.
/// - throws: Any error occurred during execution.
override func execute() throws -> [PluginizedProcessedDependencyProvider] {
let result = providers.compactMap { (provider: DependencyProvider) -> PluginizedProcessedDependencyProvider? in
if provider.pathContains(anyOf: nonCoreComponentNames) {
return process(provider, withAuxillaryPropertiesFrom: nonCoreComponentMap, auxillarySourceType: .nonCoreComponent)
} else {
return process(provider, withAuxillaryPropertiesFrom: pluginExtensionMap, auxillarySourceType: .pluginExtension)
}
}
if result.count < providers.count {
throw DependencyProviderContentError.missingDependency("Missing one or more dependencies at scope.")
}
return result
}
// MARK: - Private
private struct AuxillaryProperties {
let sourceName: String
let properties: [Property]
}
private let providers: [DependencyProvider]
private let nonCoreComponentNames: Set<String>
// The key is the (class) name of the pluginized component.
private let nonCoreComponentMap: [String: AuxillaryProperties]
// The key is the (class) name of the pluginized component.
private let pluginExtensionMap: [String: AuxillaryProperties]
// The dependency protocol name of the parent of the auxilary property's source.
// For a property from a plugin extension, the parent is the pluginized component.
// For a property from a non-core component, the parent is the corresponding core
// component.
// For instance, [FooNonCoreComponent: FooDependency] for a non-core component
// auxilary property. Or [FooPluginExtension: FooDependency] for a plugin
// extension auxilary property.
private let auxilarySourceParentDependency: [String: String]
private func process(_ provider: DependencyProvider, withAuxillaryPropertiesFrom auxillaryPropertyMap: [String: AuxillaryProperties], auxillarySourceType: AuxillarySourceType) -> PluginizedProcessedDependencyProvider? {
var levelMap = [String: Int]()
let properties = provider.dependency.properties.compactMap { (property : Property) -> PluginizedProcessedProperty? in
// Drop first element, since we should not search in the current scope.
let searchPath = provider.path.reversed().dropFirst()
// Level start at 1, since we dropped the current scope.
var level = 1
for component in searchPath {
if component.properties.contains(property) {
levelMap[component.name] = level
return PluginizedProcessedProperty(data: ProcessedProperty(unprocessed: property, sourceComponentType: component.name), auxillarySourceType: nil, auxillarySourceName: nil)
} else if let auxillaryProperties = auxillaryPropertyMap[component.name] {
// Do not search at the current auxilary scope.
let isAtCurrentAuxilaryScope = auxilarySourceParentDependency[auxillaryProperties.sourceName] == provider.dependency.name
if !isAtCurrentAuxilaryScope && auxillaryProperties.properties.contains(property) {
levelMap[component.name] = level
return PluginizedProcessedProperty(data: ProcessedProperty(unprocessed: property, sourceComponentType: component.name), auxillarySourceType: auxillarySourceType, auxillarySourceName: auxillaryProperties.sourceName)
}
}
level += 1
}
var possibleMatches = [String]()
var possibleMatchComponent: String?
// Second pass, this time only match types to produce helpful warnings
for component in searchPath {
possibleMatches = component.properties.compactMap { componentProperty in
if componentProperty.type == property.type {
return componentProperty.name
} else {
return nil
}
}
if !possibleMatches.isEmpty {
possibleMatchComponent = component.name
break
}
if let auxillaryProperties = auxillaryPropertyMap[component.name] {
possibleMatches = auxillaryProperties.properties.compactMap { componentProperty in
if componentProperty.type == property.type {
return componentProperty.name
} else {
return nil
}
}
if !possibleMatches.isEmpty {
possibleMatchComponent = auxillaryProperties.sourceName
break
}
}
}
// Throw error with informative message.
var message = "Could not find a provider for (\(property.name): \(property.type)) which was required by \(provider.dependency.name), along the DI branch of \(provider.pathString)."
if let possibleMatchComponent = possibleMatchComponent {
message += " Found possible matches \(possibleMatches) at \(possibleMatchComponent)."
}
warning(message)
return nil
}
if properties.count < provider.dependency.properties.count {
return nil
}
return PluginizedProcessedDependencyProvider(unprocessed: provider, levelMap: levelMap, processedProperties: properties)
}
}
private extension DependencyProvider {
func pathContains(anyOf names: Set<String>) -> Bool {
for component in path {
if names.contains(component.name) {
return true
}
}
return false
}
}