1// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package compliance
16
17// EdgeContextProvider is an interface for injecting edge-specific context
18// into walk paths.
19type EdgeContextProvider interface {
20	// Context returns the context for `edge` when added to `path`.
21	Context(lg *LicenseGraph, path TargetEdgePath, edge *TargetEdge) interface{}
22}
23
24// NoEdgeContext implements EdgeContextProvider for walks that use no context.
25type NoEdgeContext struct{}
26
27// Context returns nil.
28func (ctx NoEdgeContext) Context(lg *LicenseGraph, path TargetEdgePath, edge *TargetEdge) interface{} {
29	return nil
30}
31
32// ApplicableConditionsContext provides the subset of conditions in `universe`
33// that apply to each edge in a path.
34type ApplicableConditionsContext struct {
35	universe LicenseConditionSet
36}
37
38// Context returns the LicenseConditionSet applicable to the edge.
39func (ctx ApplicableConditionsContext) Context(lg *LicenseGraph, path TargetEdgePath, edge *TargetEdge) interface{} {
40	universe := ctx.universe
41	if len(path) > 0 {
42		universe = path[len(path)-1].ctx.(LicenseConditionSet)
43	}
44	return conditionsAttachingAcrossEdge(lg, edge, universe)
45}
46
47// VisitNode is called for each root and for each walked dependency node by
48// WalkTopDown and WalkTopDownBreadthFirst. When VisitNode returns true, WalkTopDown will proceed to walk
49// down the dependences of the node
50type VisitNode func(lg *LicenseGraph, target *TargetNode, path TargetEdgePath) bool
51
52// WalkTopDown does a top-down walk of `lg` calling `visit` and descending
53// into depenencies when `visit` returns true.
54func WalkTopDown(ctx EdgeContextProvider, lg *LicenseGraph, visit VisitNode) {
55	path := NewTargetEdgePath(32)
56
57	var walk func(fnode *TargetNode)
58	walk = func(fnode *TargetNode) {
59		visitChildren := visit(lg, fnode, *path)
60		if !visitChildren {
61			return
62		}
63		for _, edge := range fnode.edges {
64			var edgeContext interface{}
65			if ctx == nil {
66				edgeContext = nil
67			} else {
68				edgeContext = ctx.Context(lg, *path, edge)
69			}
70			path.Push(edge, edgeContext)
71			walk(edge.dependency)
72			path.Pop()
73		}
74	}
75
76	for _, r := range lg.rootFiles {
77		path.Clear()
78		walk(lg.targets[r])
79	}
80}
81
82// WalkTopDownBreadthFirst performs a Breadth-first top down walk of `lg` calling `visit` and descending
83// into depenencies when `visit` returns true.
84func WalkTopDownBreadthFirst(ctx EdgeContextProvider, lg *LicenseGraph, visit VisitNode) {
85	path := NewTargetEdgePath(32)
86
87	var walk func(fnode *TargetNode)
88	walk = func(fnode *TargetNode) {
89		edgesToWalk := make(TargetEdgeList, 0, len(fnode.edges))
90		for _, edge := range fnode.edges {
91			var edgeContext interface{}
92			if ctx == nil {
93				edgeContext = nil
94			} else {
95				edgeContext = ctx.Context(lg, *path, edge)
96			}
97			path.Push(edge, edgeContext)
98			if visit(lg, edge.dependency, *path){
99				edgesToWalk = append(edgesToWalk, edge)
100			}
101			path.Pop()
102		}
103
104		for _, edge := range(edgesToWalk) {
105			var edgeContext interface{}
106			if ctx == nil {
107				edgeContext = nil
108			} else {
109				edgeContext = ctx.Context(lg, *path, edge)
110			}
111			path.Push(edge, edgeContext)
112			walk(edge.dependency)
113			path.Pop()
114		}
115	}
116
117	path.Clear()
118	rootsToWalk := make([]*TargetNode, 0, len(lg.rootFiles))
119	for _, r := range lg.rootFiles {
120		if visit(lg, lg.targets[r], *path){
121			rootsToWalk = append(rootsToWalk, lg.targets[r])
122		}
123	}
124
125	for _, rnode := range(rootsToWalk) {
126		walk(rnode)
127	}
128}
129
130// resolutionKey identifies results from walking a specific target for a
131// specific set of conditions.
132type resolutionKey struct {
133	target *TargetNode
134	cs     LicenseConditionSet
135}
136
137// WalkResolutionsForCondition performs a top-down walk of the LicenseGraph
138// resolving all distributed works for `conditions`.
139func WalkResolutionsForCondition(lg *LicenseGraph, conditions LicenseConditionSet) ResolutionSet {
140	shipped := ShippedNodes(lg)
141
142	// rmap maps 'attachesTo' targets to the `actsOn` targets and applicable conditions
143	rmap := make(map[resolutionKey]ActionSet)
144
145	// cmap identifies previously walked target/condition pairs.
146	cmap := make(map[resolutionKey]struct{})
147
148	// result accumulates the resolutions to return.
149	result := make(ResolutionSet)
150	WalkTopDown(ApplicableConditionsContext{conditions}, lg, func(lg *LicenseGraph, tn *TargetNode, path TargetEdgePath) bool {
151		universe := conditions
152		if len(path) > 0 {
153			universe = path[len(path)-1].ctx.(LicenseConditionSet)
154		}
155
156		if universe.IsEmpty() {
157			return false
158		}
159		key := resolutionKey{tn, universe}
160
161		if _, alreadyWalked := cmap[key]; alreadyWalked {
162			pure := true
163			for _, p := range path {
164				target := p.Target()
165				tkey := resolutionKey{target, universe}
166				if _, ok := rmap[tkey]; !ok {
167					rmap[tkey] = make(ActionSet)
168				}
169				// attach prior walk outcome to ancestor
170				for actsOn, cs := range rmap[key] {
171					rmap[tkey][actsOn] = cs
172				}
173				// if prior walk produced results, copy results
174				// to ancestor.
175				if _, ok := result[tn]; ok && pure {
176					if _, ok := result[target]; !ok {
177						result[target] = make(ActionSet)
178					}
179					for actsOn, cs := range result[tn] {
180						result[target][actsOn] = cs
181					}
182					pure = target.IsContainer()
183				}
184			}
185			// if all ancestors are pure aggregates, attach
186			// matching prior walk conditions to self. Prior walk
187			// will not have done so if any ancestor was not an
188			// aggregate.
189			if pure {
190				match := rmap[key][tn].Intersection(universe)
191				if !match.IsEmpty() {
192					if _, ok := result[tn]; !ok {
193						result[tn] = make(ActionSet)
194					}
195					result[tn][tn] = match
196				}
197			}
198			return false
199		}
200		// no need to walk node or dependencies if not shipped
201		if !shipped.Contains(tn) {
202			return false
203		}
204		if _, ok := rmap[key]; !ok {
205			rmap[key] = make(ActionSet)
206		}
207		// add self to walk outcome
208		rmap[key][tn] = tn.resolution
209		cmap[key] = struct{}{}
210		cs := tn.resolution
211		if !cs.IsEmpty() {
212			cs = cs.Intersection(universe)
213			pure := true
214			for _, p := range path {
215				target := p.Target()
216				tkey := resolutionKey{target, universe}
217				if _, ok := rmap[tkey]; !ok {
218					rmap[tkey] = make(ActionSet)
219				}
220				// copy current node's action into ancestor
221				rmap[tkey][tn] = tn.resolution
222				// conditionally put matching conditions into
223				// result
224				if pure && !cs.IsEmpty() {
225					if _, ok := result[target]; !ok {
226						result[target] = make(ActionSet)
227					}
228					result[target][tn] = cs
229					pure = target.IsContainer()
230				}
231			}
232			// if all ancestors are pure aggregates, attach
233			// matching conditions to self.
234			if pure && !cs.IsEmpty() {
235				if _, ok := result[tn]; !ok {
236					result[tn] = make(ActionSet)
237				}
238				result[tn][tn] = cs
239			}
240		}
241		return true
242	})
243
244	return result
245}
246
247// WalkActionsForCondition performs a top-down walk of the LicenseGraph
248// resolving all distributed works for `conditions`.
249func WalkActionsForCondition(lg *LicenseGraph, conditions LicenseConditionSet) ActionSet {
250	// amap maps 'actsOn' targets to the applicable conditions
251	//
252	// amap is the resulting ActionSet
253	amap := make(ActionSet)
254
255	for tn := range ShippedNodes(lg) {
256		if cs := conditions.Intersection(tn.resolution); !cs.IsEmpty() {
257			amap[tn] = cs
258		}
259	}
260
261	return amap
262}
263