blob: b31208d5ee2e68c46a9354c567ec3d44eeecea71 [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
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.
14package com.google.devtools.build.lib.analysis;
15
16import com.google.common.base.Joiner;
tomlua155b532017-11-08 20:12:47 +010017import com.google.common.base.Preconditions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010018import com.google.common.collect.ImmutableList;
19import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.Lists;
Rumou Duan33bab462016-04-25 17:55:12 +000021import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
tomlu3d1a1942017-11-29 14:01:21 -080022import com.google.devtools.build.lib.actions.ActionKeyContext;
janakr658d47f2019-05-29 11:11:30 -070023import com.google.devtools.build.lib.actions.ActionLookupValue;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010024import com.google.devtools.build.lib.actions.Artifact;
cpeyserac09f0a2018-02-05 09:33:15 -080025import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010026import com.google.devtools.build.lib.actions.ArtifactFactory;
tomlu1cdcdf92018-01-16 11:07:51 -080027import com.google.devtools.build.lib.actions.ArtifactRoot;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010028import com.google.devtools.build.lib.actions.MiddlemanFactory;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010029import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoCollection;
Janak Ramakrishnanf27f4382015-06-04 19:50:15 +000030import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010031import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory.BuildInfoKey;
Janak Ramakrishnanf27f4382015-06-04 19:50:15 +000032import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
Klaus Aehlig777b30d2017-02-24 16:30:15 +000033import com.google.devtools.build.lib.events.ExtendedEventHandler;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010034import com.google.devtools.build.lib.events.StoredEventHandler;
35import com.google.devtools.build.lib.packages.Target;
36import com.google.devtools.build.lib.skyframe.BuildInfoCollectionValue;
Janak Ramakrishnanf27f4382015-06-04 19:50:15 +000037import com.google.devtools.build.lib.skyframe.PrecomputedValue;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010038import com.google.devtools.build.lib.skyframe.WorkspaceStatusValue;
laurentlb6659b4c2019-02-18 07:23:36 -080039import com.google.devtools.build.lib.syntax.StarlarkSemantics;
janakr9dd7e8e2019-05-29 13:57:16 -070040import com.google.devtools.build.lib.util.Pair;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010041import com.google.devtools.build.lib.vfs.PathFragment;
42import com.google.devtools.build.skyframe.SkyFunction;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010043import java.io.PrintWriter;
44import java.io.StringWriter;
45import java.util.ArrayList;
46import java.util.Collection;
rosica246c2aa2019-01-15 09:55:34 -080047import java.util.Collections;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010048import java.util.HashMap;
49import java.util.HashSet;
50import java.util.List;
51import java.util.Map;
52import java.util.Set;
53import java.util.TreeMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010054import javax.annotation.Nullable;
55
56/**
57 * The implementation of AnalysisEnvironment used for analysis. It tracks metadata for each
58 * configured target, such as the errors and warnings emitted by that target. It is intended that
59 * a separate instance is used for each configured target, so that these don't mix up.
60 */
61public class CachingAnalysisEnvironment implements AnalysisEnvironment {
62 private final ArtifactFactory artifactFactory;
63
janakr658d47f2019-05-29 11:11:30 -070064 private final ActionLookupValue.ActionLookupKey owner;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010065 /**
66 * If this is the system analysis environment, then errors and warnings are directly reported
67 * to the global reporter, rather than stored, i.e., we don't track here whether there are any
68 * errors.
69 */
70 private final boolean isSystemEnv;
71 private final boolean extendedSanityChecks;
cparsons9b8c5002019-03-21 14:31:16 -070072 private final boolean allowAnalysisFailures;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010073
tomlu3d1a1942017-11-29 14:01:21 -080074 private final ActionKeyContext actionKeyContext;
75
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010076 private boolean enabled = true;
77 private MiddlemanFactory middlemanFactory;
Klaus Aehlig777b30d2017-02-24 16:30:15 +000078 private ExtendedEventHandler errorEventListener;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010079 private SkyFunction.Environment skyframeEnv;
janakr9dd7e8e2019-05-29 13:57:16 -070080 /**
81 * Map of artifacts to either themselves or to {@code Pair<Artifact, String>} if
82 * --experimental_extended_sanity_checks is enabled. In the latter case, the string will contain
83 * the stack trace of where the artifact was created. In the former case, we'll construct a
84 * generic message in case of error.
85 *
86 * <p>The artifact is stored so that we can deduplicate artifacts created multiple times.
87 */
88 private Map<Artifact, Object> artifacts;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010089
90 /**
91 * The list of actions registered by the configured target this analysis environment is
92 * responsible for. May get cleared out at the end of the analysis of said target.
93 */
Rumou Duan33bab462016-04-25 17:55:12 +000094 final List<ActionAnalysisMetadata> actions = new ArrayList<>();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010095
Klaus Aehlig777b30d2017-02-24 16:30:15 +000096 public CachingAnalysisEnvironment(
97 ArtifactFactory artifactFactory,
tomlu3d1a1942017-11-29 14:01:21 -080098 ActionKeyContext actionKeyContext,
janakr658d47f2019-05-29 11:11:30 -070099 ActionLookupValue.ActionLookupKey owner,
Klaus Aehlig777b30d2017-02-24 16:30:15 +0000100 boolean isSystemEnv,
101 boolean extendedSanityChecks,
cparsons9b8c5002019-03-21 14:31:16 -0700102 boolean allowAnalysisFailures,
Klaus Aehlig777b30d2017-02-24 16:30:15 +0000103 ExtendedEventHandler errorEventListener,
shahan52bb22a2018-09-02 08:39:50 -0700104 SkyFunction.Environment env) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100105 this.artifactFactory = artifactFactory;
tomlu3d1a1942017-11-29 14:01:21 -0800106 this.actionKeyContext = actionKeyContext;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100107 this.owner = Preconditions.checkNotNull(owner);
108 this.isSystemEnv = isSystemEnv;
109 this.extendedSanityChecks = extendedSanityChecks;
cparsons9b8c5002019-03-21 14:31:16 -0700110 this.allowAnalysisFailures = allowAnalysisFailures;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100111 this.errorEventListener = errorEventListener;
112 this.skyframeEnv = env;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100113 middlemanFactory = new MiddlemanFactory(artifactFactory, this);
114 artifacts = new HashMap<>();
115 }
116
117 public void disable(Target target) {
cparsons9b8c5002019-03-21 14:31:16 -0700118 if (!hasErrors() && !allowAnalysisFailures) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100119 verifyGeneratedArtifactHaveActions(target);
120 }
121 artifacts = null;
122 middlemanFactory = null;
123 enabled = false;
124 errorEventListener = null;
125 skyframeEnv = null;
126 }
127
Rumou Duan33bab462016-04-25 17:55:12 +0000128 private static StringBuilder shortDescription(ActionAnalysisMetadata action) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100129 if (action == null) {
130 return new StringBuilder("null Action");
131 }
132 return new StringBuilder()
133 .append(action.getClass().getName())
134 .append(' ')
135 .append(action.getMnemonic());
136 }
137
138 /**
139 * Sanity checks that all generated artifacts have a generating action.
140 * @param target for error reporting
141 */
142 public void verifyGeneratedArtifactHaveActions(Target target) {
143 Collection<String> orphanArtifacts = getOrphanArtifactMap().values();
144 List<String> checkedActions = null;
145 if (!orphanArtifacts.isEmpty()) {
146 checkedActions = Lists.newArrayListWithCapacity(actions.size());
Rumou Duan33bab462016-04-25 17:55:12 +0000147 for (ActionAnalysisMetadata action : actions) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100148 StringBuilder sb = shortDescription(action);
149 for (Artifact o : action.getOutputs()) {
150 sb.append("\n ");
151 sb.append(o.getExecPathString());
152 }
153 checkedActions.add(sb.toString());
154 }
155 throw new IllegalStateException(
156 String.format(
Alex Humesky14f79d52015-07-08 18:39:15 +0000157 "%s %s : These artifacts do not have a generating action:\n%s\n"
158 + "These actions were checked:\n%s\n",
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100159 target.getTargetKind(), target.getLabel(),
160 Joiner.on('\n').join(orphanArtifacts), Joiner.on('\n').join(checkedActions)));
161 }
162 }
163
164 @Override
165 public ImmutableSet<Artifact> getOrphanArtifacts() {
166 return ImmutableSet.copyOf(getOrphanArtifactMap().keySet());
167 }
168
Dmitry Lomov5b1ce4d2018-05-30 04:34:08 -0700169 @Override
170 public ImmutableSet<Artifact> getTreeArtifactsConflictingWithFiles() {
Dmitry Lomov5b1ce4d2018-05-30 04:34:08 -0700171 boolean hasTreeArtifacts = false;
172 for (Artifact artifact : artifacts.keySet()) {
173 if (artifact.isTreeArtifact()) {
174 hasTreeArtifacts = true;
175 break;
176 }
177 }
178 if (!hasTreeArtifacts) {
179 return ImmutableSet.of();
180 }
181
182 HashSet<PathFragment> collect = new HashSet<>();
183 for (Artifact artifact : artifacts.keySet()) {
184 if (!artifact.isSourceArtifact() && !artifact.isTreeArtifact()) {
185 collect.add(artifact.getExecPath());
186 }
187 }
188
189 ImmutableSet.Builder<Artifact> sameExecPathTreeArtifacts = ImmutableSet.builder();
190 for (Artifact artifact : artifacts.keySet()) {
191 if (artifact.isTreeArtifact() && collect.contains(artifact.getExecPath())) {
192 sameExecPathTreeArtifacts.add(artifact);
193 }
194 }
195
196 return sameExecPathTreeArtifacts.build();
197 }
198
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100199 private Map<Artifact, String> getOrphanArtifactMap() {
200 // Construct this set to avoid poor performance under large --runs_per_test.
201 Set<Artifact> artifactsWithActions = new HashSet<>();
Rumou Duan33bab462016-04-25 17:55:12 +0000202 for (ActionAnalysisMetadata action : actions) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100203 // Don't bother checking that every Artifact only appears once; that test is performed
204 // elsewhere (see #testNonUniqueOutputs in ActionListenerIntegrationTest).
205 artifactsWithActions.addAll(action.getOutputs());
206 }
207 // The order of the artifacts.entrySet iteration is unspecified - we use a TreeMap here to
208 // guarantee that the return value of this method is deterministic.
Lukacs Berki8ae34f12015-04-10 14:54:19 +0000209 Map<Artifact, String> orphanArtifacts = new TreeMap<>(Artifact.EXEC_PATH_COMPARATOR);
janakr9dd7e8e2019-05-29 13:57:16 -0700210 for (Map.Entry<Artifact, Object> entry : artifacts.entrySet()) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100211 Artifact a = entry.getKey();
212 if (!a.isSourceArtifact() && !artifactsWithActions.contains(a)) {
janakr9dd7e8e2019-05-29 13:57:16 -0700213 Object value = entry.getValue();
214 if (value instanceof Artifact) {
215 value = "No origin, run with --experimental_extended_sanity_checks";
216 } else {
217 value = ((Pair<?, ?>) value).second;
218 }
219 orphanArtifacts.put(
220 a,
221 String.format(
222 "%s\n%s",
223 a.getExecPathString(), // uncovered artifact
224 value)); // origin of creation
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100225 }
226 }
227 return orphanArtifacts;
228 }
229
230 @Override
Klaus Aehlig777b30d2017-02-24 16:30:15 +0000231 public ExtendedEventHandler getEventHandler() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100232 return errorEventListener;
233 }
234
235 @Override
tomlu3d1a1942017-11-29 14:01:21 -0800236 public ActionKeyContext getActionKeyContext() {
237 return actionKeyContext;
238 }
239
240 @Override
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100241 public boolean hasErrors() {
242 // The system analysis environment never has errors.
243 if (isSystemEnv) {
244 return false;
245 }
246 Preconditions.checkState(enabled);
247 return ((StoredEventHandler) errorEventListener).hasErrors();
248 }
249
250 @Override
251 public MiddlemanFactory getMiddlemanFactory() {
252 Preconditions.checkState(enabled);
253 return middlemanFactory;
254 }
255
256 /**
257 * Keeps track of artifacts. We check that all of them have an owner when the environment is
258 * sealed (disable()). For performance reasons we only track the originating stacktrace when
259 * running with --experimental_extended_sanity_checks.
260 */
janakr9dd7e8e2019-05-29 13:57:16 -0700261 @SuppressWarnings("unchecked") // Cast of artifacts map's value to Pair.
262 private Artifact.DerivedArtifact dedupAndTrackArtifactAndOrigin(
janakr658d47f2019-05-29 11:11:30 -0700263 Artifact.DerivedArtifact a, @Nullable Throwable e) {
janakr9dd7e8e2019-05-29 13:57:16 -0700264 if (artifacts.containsKey(a)) {
265 Object value = artifacts.get(a);
266 if (e == null) {
267 return (Artifact.DerivedArtifact) value;
268 } else {
269 return ((Pair<Artifact.DerivedArtifact, String>) value).first;
270 }
271 }
272 if ((e != null)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100273 StringWriter sw = new StringWriter();
274 e.printStackTrace(new PrintWriter(sw));
janakr9dd7e8e2019-05-29 13:57:16 -0700275 artifacts.put(a, Pair.of(a, sw.toString()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100276 } else {
janakr9dd7e8e2019-05-29 13:57:16 -0700277 artifacts.put(a, a);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100278 }
279 return a;
280 }
281
282 @Override
janakr658d47f2019-05-29 11:11:30 -0700283 public Artifact.DerivedArtifact getDerivedArtifact(
284 PathFragment rootRelativePath, ArtifactRoot root) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100285 Preconditions.checkState(enabled);
janakr9dd7e8e2019-05-29 13:57:16 -0700286 return dedupAndTrackArtifactAndOrigin(
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100287 artifactFactory.getDerivedArtifact(rootRelativePath, root, getOwner()),
288 extendedSanityChecks ? new Throwable() : null);
289 }
290
291 @Override
cpeyserac09f0a2018-02-05 09:33:15 -0800292 public SpecialArtifact getTreeArtifact(PathFragment rootRelativePath, ArtifactRoot root) {
Michael Thvedte3b1cb72016-02-08 23:32:27 +0000293 Preconditions.checkState(enabled);
cpeyserac09f0a2018-02-05 09:33:15 -0800294 return (SpecialArtifact)
janakr9dd7e8e2019-05-29 13:57:16 -0700295 dedupAndTrackArtifactAndOrigin(
cpeyserac09f0a2018-02-05 09:33:15 -0800296 artifactFactory.getTreeArtifact(rootRelativePath, root, getOwner()),
297 extendedSanityChecks ? new Throwable() : null);
Michael Thvedte3b1cb72016-02-08 23:32:27 +0000298 }
299
300 @Override
janakr658d47f2019-05-29 11:11:30 -0700301 public Artifact.DerivedArtifact getFilesetArtifact(
302 PathFragment rootRelativePath, ArtifactRoot root) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100303 Preconditions.checkState(enabled);
janakr9dd7e8e2019-05-29 13:57:16 -0700304 return dedupAndTrackArtifactAndOrigin(
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100305 artifactFactory.getFilesetArtifact(rootRelativePath, root, getOwner()),
306 extendedSanityChecks ? new Throwable() : null);
307 }
308
309 @Override
tomlu1cdcdf92018-01-16 11:07:51 -0800310 public Artifact getConstantMetadataArtifact(PathFragment rootRelativePath, ArtifactRoot root) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100311 return artifactFactory.getConstantMetadataArtifact(rootRelativePath, root, getOwner());
312 }
313
314 @Override
Rumou Duan33bab462016-04-25 17:55:12 +0000315 public void registerAction(ActionAnalysisMetadata... actions) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100316 Preconditions.checkState(enabled);
rosica246c2aa2019-01-15 09:55:34 -0800317 Collections.addAll(this.actions, actions);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100318 }
319
320 @Override
Rumou Duan33bab462016-04-25 17:55:12 +0000321 public ActionAnalysisMetadata getLocalGeneratingAction(Artifact artifact) {
Rumou Duan33bab462016-04-25 17:55:12 +0000322 for (ActionAnalysisMetadata action : actions) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100323 if (action.getOutputs().contains(artifact)) {
324 return action;
325 }
326 }
327 return null;
328 }
329
330 @Override
tomlu9e91f202018-06-18 16:16:36 -0700331 public ImmutableList<ActionAnalysisMetadata> getRegisteredActions() {
332 return ImmutableList.copyOf(actions);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100333 }
334
335 @Override
336 public SkyFunction.Environment getSkyframeEnv() {
337 return skyframeEnv;
338 }
339
340 @Override
laurentlb6659b4c2019-02-18 07:23:36 -0800341 public StarlarkSemantics getSkylarkSemantics() throws InterruptedException {
laurentlb8c02aff2019-02-18 10:53:34 -0800342 return PrecomputedValue.STARLARK_SEMANTICS.get(skyframeEnv);
brandjonb712f332017-04-29 16:03:32 +0200343 }
344
345 @Override
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000346 public Artifact getStableWorkspaceStatusArtifact() throws InterruptedException {
janakr573807d2018-01-11 14:02:35 -0800347 return ((WorkspaceStatusValue) skyframeEnv.getValue(WorkspaceStatusValue.BUILD_INFO_KEY))
348 .getStableArtifact();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100349 }
350
351 @Override
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000352 public Artifact getVolatileWorkspaceStatusArtifact() throws InterruptedException {
janakr573807d2018-01-11 14:02:35 -0800353 return ((WorkspaceStatusValue) skyframeEnv.getValue(WorkspaceStatusValue.BUILD_INFO_KEY))
354 .getVolatileArtifact();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100355 }
356
Janak Ramakrishnanf27f4382015-06-04 19:50:15 +0000357 // See SkyframeBuildView#getWorkspaceStatusValues for the code that this method is attempting to
358 // verify.
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000359 private NullPointerException collectDebugInfoAndCrash(BuildInfoKey key, BuildConfiguration config)
360 throws InterruptedException {
Janak Ramakrishnanf27f4382015-06-04 19:50:15 +0000361 String debugInfo = key + " " + config;
362 Preconditions.checkState(skyframeEnv.valuesMissing(), debugInfo);
363 Map<BuildInfoKey, BuildInfoFactory> buildInfoFactories = Preconditions.checkNotNull(
364 PrecomputedValue.BUILD_INFO_FACTORIES.get(skyframeEnv), debugInfo);
365 BuildInfoFactory buildInfoFactory =
366 Preconditions.checkNotNull(buildInfoFactories.get(key), debugInfo);
367 Preconditions.checkState(buildInfoFactory.isEnabled(config), debugInfo);
368 throw new NullPointerException("BuildInfoCollectionValue shouldn't have been null");
369 }
370
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100371 @Override
Googler38ad0bf2016-07-01 05:00:14 +0000372 public ImmutableList<Artifact> getBuildInfo(
plf1f341a62019-04-01 14:02:14 -0700373 boolean stamp, BuildInfoKey key, BuildConfiguration config) throws InterruptedException {
Janak Ramakrishnanf27f4382015-06-04 19:50:15 +0000374 BuildInfoCollectionValue collectionValue =
janakr573807d2018-01-11 14:02:35 -0800375 (BuildInfoCollectionValue) skyframeEnv.getValue(BuildInfoCollectionValue.key(key, config));
Janak Ramakrishnanf27f4382015-06-04 19:50:15 +0000376 if (collectionValue == null) {
Googler38ad0bf2016-07-01 05:00:14 +0000377 throw collectDebugInfoAndCrash(key, config);
Janak Ramakrishnanf27f4382015-06-04 19:50:15 +0000378 }
379 BuildInfoCollection collection = collectionValue.getCollection();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100380 return stamp ? collection.getStampedBuildInfo() : collection.getRedactedBuildInfo();
381 }
382
383 @Override
janakr658d47f2019-05-29 11:11:30 -0700384 public ActionLookupValue.ActionLookupKey getOwner() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100385 return owner;
386 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100387}