001package org.apache.maven.lifecycle.internal; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.io.IOException; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.HashSet; 028import java.util.List; 029import java.util.Map; 030 031import org.apache.maven.execution.MavenSession; 032import org.apache.maven.lifecycle.DefaultLifecycles; 033import org.apache.maven.lifecycle.Lifecycle; 034import org.apache.maven.lifecycle.LifecycleMappingDelegate; 035import org.apache.maven.lifecycle.LifecycleNotFoundException; 036import org.apache.maven.lifecycle.LifecyclePhaseNotFoundException; 037import org.apache.maven.lifecycle.MavenExecutionPlan; 038import org.apache.maven.lifecycle.MojoExecutionConfigurator; 039import org.apache.maven.lifecycle.internal.builder.BuilderCommon; 040import org.apache.maven.plugin.BuildPluginManager; 041import org.apache.maven.plugin.InvalidPluginDescriptorException; 042import org.apache.maven.plugin.MojoExecution; 043import org.apache.maven.plugin.MojoNotFoundException; 044import org.apache.maven.plugin.PluginDescriptorParsingException; 045import org.apache.maven.plugin.PluginNotFoundException; 046import org.apache.maven.plugin.PluginResolutionException; 047import org.apache.maven.plugin.descriptor.MojoDescriptor; 048import org.apache.maven.plugin.descriptor.Parameter; 049import org.apache.maven.plugin.descriptor.PluginDescriptor; 050import org.apache.maven.plugin.lifecycle.Execution; 051import org.apache.maven.plugin.lifecycle.Phase; 052import org.apache.maven.plugin.prefix.NoPluginFoundForPrefixException; 053import org.apache.maven.plugin.version.PluginVersionResolutionException; 054import org.apache.maven.plugin.version.PluginVersionResolver; 055import org.apache.maven.project.MavenProject; 056import org.codehaus.plexus.component.annotations.Component; 057import org.codehaus.plexus.component.annotations.Requirement; 058import org.codehaus.plexus.util.StringUtils; 059import org.codehaus.plexus.util.xml.Xpp3Dom; 060import org.codehaus.plexus.util.xml.pull.XmlPullParserException; 061 062import com.google.common.collect.ImmutableMap; 063 064/** 065 * @since 3.0 066 * @author Benjamin Bentmann 067 * @author Kristian Rosenvold (Extract class) 068 * <p/> 069 * NOTE: This class is not part of any public api and can be changed or deleted without prior notice. 070 */ 071@Component( role = LifecycleExecutionPlanCalculator.class ) 072public class DefaultLifecycleExecutionPlanCalculator 073 implements LifecycleExecutionPlanCalculator 074{ 075 @Requirement 076 private PluginVersionResolver pluginVersionResolver; 077 078 @Requirement 079 private BuildPluginManager pluginManager; 080 081 @Requirement 082 private DefaultLifecycles defaultLifeCycles; 083 084 @Requirement 085 private MojoDescriptorCreator mojoDescriptorCreator; 086 087 @Requirement 088 private LifecyclePluginResolver lifecyclePluginResolver; 089 090 @Requirement( hint = DefaultLifecycleMappingDelegate.HINT ) 091 private LifecycleMappingDelegate standardDelegate; 092 093 @Requirement 094 private Map<String, LifecycleMappingDelegate> delegates; 095 096 @Requirement 097 private Map<String, MojoExecutionConfigurator> mojoExecutionConfigurators; 098 099 @SuppressWarnings( { "UnusedDeclaration" } ) 100 public DefaultLifecycleExecutionPlanCalculator() 101 { 102 } 103 104 // Only used for testing 105 public DefaultLifecycleExecutionPlanCalculator( BuildPluginManager pluginManager, 106 DefaultLifecycles defaultLifeCycles, 107 MojoDescriptorCreator mojoDescriptorCreator, 108 LifecyclePluginResolver lifecyclePluginResolver ) 109 { 110 this.pluginManager = pluginManager; 111 this.defaultLifeCycles = defaultLifeCycles; 112 this.mojoDescriptorCreator = mojoDescriptorCreator; 113 this.lifecyclePluginResolver = lifecyclePluginResolver; 114 this.mojoExecutionConfigurators = 115 ImmutableMap.of( "default", (MojoExecutionConfigurator) new DefaultMojoExecutionConfigurator() ); 116 } 117 118 @Override 119 public MavenExecutionPlan calculateExecutionPlan( MavenSession session, MavenProject project, List<Object> tasks, 120 boolean setup ) 121 throws PluginNotFoundException, PluginResolutionException, LifecyclePhaseNotFoundException, 122 PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException, 123 NoPluginFoundForPrefixException, LifecycleNotFoundException, PluginVersionResolutionException 124 { 125 lifecyclePluginResolver.resolveMissingPluginVersions( project, session ); 126 127 final List<MojoExecution> executions = calculateMojoExecutions( session, project, tasks ); 128 129 if ( setup ) 130 { 131 setupMojoExecutions( session, project, executions ); 132 } 133 134 final List<ExecutionPlanItem> planItem = ExecutionPlanItem.createExecutionPlanItems( project, executions ); 135 136 return new MavenExecutionPlan( planItem, defaultLifeCycles ); 137 } 138 139 @Override 140 public MavenExecutionPlan calculateExecutionPlan( MavenSession session, MavenProject project, List<Object> tasks ) 141 throws PluginNotFoundException, PluginResolutionException, LifecyclePhaseNotFoundException, 142 PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException, 143 NoPluginFoundForPrefixException, LifecycleNotFoundException, PluginVersionResolutionException 144 { 145 return calculateExecutionPlan( session, project, tasks, true ); 146 } 147 148 private void setupMojoExecutions( MavenSession session, MavenProject project, List<MojoExecution> mojoExecutions ) 149 throws PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException, 150 MojoNotFoundException, InvalidPluginDescriptorException, NoPluginFoundForPrefixException, 151 LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException 152 { 153 for ( MojoExecution mojoExecution : mojoExecutions ) 154 { 155 setupMojoExecution( session, project, mojoExecution ); 156 } 157 } 158 159 @Override 160 public void setupMojoExecution( MavenSession session, MavenProject project, MojoExecution mojoExecution ) 161 throws PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException, 162 MojoNotFoundException, InvalidPluginDescriptorException, NoPluginFoundForPrefixException, 163 LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException 164 { 165 MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor(); 166 167 if ( mojoDescriptor == null ) 168 { 169 mojoDescriptor = 170 pluginManager.getMojoDescriptor( mojoExecution.getPlugin(), mojoExecution.getGoal(), 171 project.getRemotePluginRepositories(), 172 session.getRepositorySession() ); 173 174 mojoExecution.setMojoDescriptor( mojoDescriptor ); 175 } 176 177 mojoExecutionConfigurator( mojoExecution ).configure( project, 178 mojoExecution, 179 MojoExecution.Source.CLI.equals( mojoExecution.getSource() ) ); 180 181 finalizeMojoConfiguration( mojoExecution ); 182 183 calculateForkedExecutions( mojoExecution, session, project, new HashSet<MojoDescriptor>() ); 184 } 185 186 public List<MojoExecution> calculateMojoExecutions( MavenSession session, MavenProject project, List<Object> tasks ) 187 throws PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException, 188 MojoNotFoundException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException, 189 PluginVersionResolutionException, LifecyclePhaseNotFoundException 190 { 191 final List<MojoExecution> mojoExecutions = new ArrayList<>(); 192 193 for ( Object task : tasks ) 194 { 195 if ( task instanceof GoalTask ) 196 { 197 String pluginGoal = ( (GoalTask) task ).pluginGoal; 198 199 String executionId = "default-cli"; 200 int executionIdx = pluginGoal.indexOf( '@' ); 201 if ( executionIdx > 0 ) 202 { 203 executionId = pluginGoal.substring( executionIdx + 1 ); 204 } 205 206 MojoDescriptor mojoDescriptor = mojoDescriptorCreator.getMojoDescriptor( pluginGoal, session, project ); 207 208 MojoExecution mojoExecution = new MojoExecution( mojoDescriptor, executionId, 209 MojoExecution.Source.CLI ); 210 211 mojoExecutions.add( mojoExecution ); 212 } 213 else if ( task instanceof LifecycleTask ) 214 { 215 String lifecyclePhase = ( (LifecycleTask) task ).getLifecyclePhase(); 216 217 Map<String, List<MojoExecution>> phaseToMojoMapping = 218 calculateLifecycleMappings( session, project, lifecyclePhase ); 219 220 for ( List<MojoExecution> mojoExecutionsFromLifecycle : phaseToMojoMapping.values() ) 221 { 222 mojoExecutions.addAll( mojoExecutionsFromLifecycle ); 223 } 224 } 225 else 226 { 227 throw new IllegalStateException( "unexpected task " + task ); 228 } 229 } 230 return mojoExecutions; 231 } 232 233 private Map<String, List<MojoExecution>> calculateLifecycleMappings( MavenSession session, MavenProject project, 234 String lifecyclePhase ) 235 throws LifecyclePhaseNotFoundException, PluginNotFoundException, PluginResolutionException, 236 PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException 237 { 238 /* 239 * Determine the lifecycle that corresponds to the given phase. 240 */ 241 242 Lifecycle lifecycle = defaultLifeCycles.get( lifecyclePhase ); 243 244 if ( lifecycle == null ) 245 { 246 throw new LifecyclePhaseNotFoundException( "Unknown lifecycle phase \"" + lifecyclePhase 247 + "\". You must specify a valid lifecycle phase" + " or a goal in the format <plugin-prefix>:<goal> or" 248 + " <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: " 249 + defaultLifeCycles.getLifecyclePhaseList() + ".", lifecyclePhase ); 250 } 251 252 LifecycleMappingDelegate delegate; 253 if ( Arrays.binarySearch( DefaultLifecycles.STANDARD_LIFECYCLES, lifecycle.getId() ) >= 0 ) 254 { 255 delegate = standardDelegate; 256 } 257 else 258 { 259 delegate = delegates.get( lifecycle.getId() ); 260 if ( delegate == null ) 261 { 262 delegate = standardDelegate; 263 } 264 } 265 266 return delegate.calculateLifecycleMappings( session, project, lifecycle, lifecyclePhase ); 267 } 268 269 /** 270 * Post-processes the effective configuration for the specified mojo execution. This step discards all parameters 271 * from the configuration that are not applicable to the mojo and injects the default values for any missing 272 * parameters. 273 * 274 * @param mojoExecution The mojo execution whose configuration should be finalized, must not be {@code null}. 275 */ 276 private void finalizeMojoConfiguration( MojoExecution mojoExecution ) 277 { 278 MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor(); 279 280 Xpp3Dom executionConfiguration = mojoExecution.getConfiguration(); 281 if ( executionConfiguration == null ) 282 { 283 executionConfiguration = new Xpp3Dom( "configuration" ); 284 } 285 286 Xpp3Dom defaultConfiguration = getMojoConfiguration( mojoDescriptor ); 287 288 Xpp3Dom finalConfiguration = new Xpp3Dom( "configuration" ); 289 290 if ( mojoDescriptor.getParameters() != null ) 291 { 292 for ( Parameter parameter : mojoDescriptor.getParameters() ) 293 { 294 Xpp3Dom parameterConfiguration = executionConfiguration.getChild( parameter.getName() ); 295 296 if ( parameterConfiguration == null ) 297 { 298 parameterConfiguration = executionConfiguration.getChild( parameter.getAlias() ); 299 } 300 301 Xpp3Dom parameterDefaults = defaultConfiguration.getChild( parameter.getName() ); 302 303 parameterConfiguration = Xpp3Dom.mergeXpp3Dom( parameterConfiguration, parameterDefaults, 304 Boolean.TRUE ); 305 306 if ( parameterConfiguration != null ) 307 { 308 parameterConfiguration = new Xpp3Dom( parameterConfiguration, parameter.getName() ); 309 310 if ( StringUtils.isEmpty( parameterConfiguration.getAttribute( "implementation" ) ) 311 && StringUtils.isNotEmpty( parameter.getImplementation() ) ) 312 { 313 parameterConfiguration.setAttribute( "implementation", parameter.getImplementation() ); 314 } 315 316 finalConfiguration.addChild( parameterConfiguration ); 317 } 318 } 319 } 320 321 mojoExecution.setConfiguration( finalConfiguration ); 322 } 323 324 private Xpp3Dom getMojoConfiguration( MojoDescriptor mojoDescriptor ) 325 { 326 return MojoDescriptorCreator.convert( mojoDescriptor ); 327 } 328 329 @Override 330 public void calculateForkedExecutions( MojoExecution mojoExecution, MavenSession session ) 331 throws MojoNotFoundException, PluginNotFoundException, PluginResolutionException, 332 PluginDescriptorParsingException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException, 333 LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException 334 { 335 calculateForkedExecutions( mojoExecution, session, session.getCurrentProject(), new HashSet<MojoDescriptor>() ); 336 } 337 338 private void calculateForkedExecutions( MojoExecution mojoExecution, MavenSession session, MavenProject project, 339 Collection<MojoDescriptor> alreadyForkedExecutions ) 340 throws MojoNotFoundException, PluginNotFoundException, PluginResolutionException, 341 PluginDescriptorParsingException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException, 342 LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException 343 { 344 MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor(); 345 346 if ( !mojoDescriptor.isForking() ) 347 { 348 return; 349 } 350 351 if ( !alreadyForkedExecutions.add( mojoDescriptor ) ) 352 { 353 return; 354 } 355 356 List<MavenProject> forkedProjects = 357 LifecycleDependencyResolver.getProjects( project, session, mojoDescriptor.isAggregator() ); 358 359 for ( MavenProject forkedProject : forkedProjects ) 360 { 361 if ( forkedProject != project ) 362 { 363 lifecyclePluginResolver.resolveMissingPluginVersions( forkedProject, session ); 364 } 365 366 List<MojoExecution> forkedExecutions; 367 368 if ( StringUtils.isNotEmpty( mojoDescriptor.getExecutePhase() ) ) 369 { 370 forkedExecutions = 371 calculateForkedLifecycle( mojoExecution, session, forkedProject, alreadyForkedExecutions ); 372 } 373 else 374 { 375 forkedExecutions = calculateForkedGoal( mojoExecution, session, forkedProject, 376 alreadyForkedExecutions ); 377 } 378 379 mojoExecution.setForkedExecutions( BuilderCommon.getKey( forkedProject ), forkedExecutions ); 380 } 381 382 alreadyForkedExecutions.remove( mojoDescriptor ); 383 } 384 385 private List<MojoExecution> calculateForkedLifecycle( MojoExecution mojoExecution, MavenSession session, 386 MavenProject project, 387 Collection<MojoDescriptor> alreadyForkedExecutions ) 388 throws MojoNotFoundException, PluginNotFoundException, PluginResolutionException, 389 PluginDescriptorParsingException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException, 390 LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException 391 { 392 MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor(); 393 394 String forkedPhase = mojoDescriptor.getExecutePhase(); 395 396 Map<String, List<MojoExecution>> lifecycleMappings = calculateLifecycleMappings( session, project, 397 forkedPhase ); 398 399 for ( List<MojoExecution> forkedExecutions : lifecycleMappings.values() ) 400 { 401 for ( MojoExecution forkedExecution : forkedExecutions ) 402 { 403 if ( forkedExecution.getMojoDescriptor() == null ) 404 { 405 MojoDescriptor forkedMojoDescriptor = 406 pluginManager.getMojoDescriptor( forkedExecution.getPlugin(), forkedExecution.getGoal(), 407 project.getRemotePluginRepositories(), 408 session.getRepositorySession() ); 409 410 forkedExecution.setMojoDescriptor( forkedMojoDescriptor ); 411 } 412 413 mojoExecutionConfigurator( forkedExecution ).configure( project, forkedExecution, false ); 414 } 415 } 416 417 injectLifecycleOverlay( lifecycleMappings, mojoExecution, session, project ); 418 419 List<MojoExecution> mojoExecutions = new ArrayList<>(); 420 421 for ( List<MojoExecution> forkedExecutions : lifecycleMappings.values() ) 422 { 423 for ( MojoExecution forkedExecution : forkedExecutions ) 424 { 425 if ( !alreadyForkedExecutions.contains( forkedExecution.getMojoDescriptor() ) ) 426 { 427 finalizeMojoConfiguration( forkedExecution ); 428 429 calculateForkedExecutions( forkedExecution, session, project, alreadyForkedExecutions ); 430 431 mojoExecutions.add( forkedExecution ); 432 } 433 } 434 } 435 436 return mojoExecutions; 437 } 438 439 private void injectLifecycleOverlay( Map<String, List<MojoExecution>> lifecycleMappings, 440 MojoExecution mojoExecution, MavenSession session, MavenProject project ) 441 throws PluginDescriptorParsingException, LifecycleNotFoundException, MojoNotFoundException, 442 PluginNotFoundException, PluginResolutionException, NoPluginFoundForPrefixException, 443 InvalidPluginDescriptorException, PluginVersionResolutionException 444 { 445 MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor(); 446 447 PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor(); 448 449 String forkedLifecycle = mojoDescriptor.getExecuteLifecycle(); 450 451 if ( StringUtils.isEmpty( forkedLifecycle ) ) 452 { 453 return; 454 } 455 456 org.apache.maven.plugin.lifecycle.Lifecycle lifecycleOverlay; 457 458 try 459 { 460 lifecycleOverlay = pluginDescriptor.getLifecycleMapping( forkedLifecycle ); 461 } 462 catch ( IOException | XmlPullParserException e ) 463 { 464 throw new PluginDescriptorParsingException( pluginDescriptor.getPlugin(), pluginDescriptor.getSource(), e ); 465 } 466 467 if ( lifecycleOverlay == null ) 468 { 469 throw new LifecycleNotFoundException( forkedLifecycle ); 470 } 471 472 for ( Phase phase : lifecycleOverlay.getPhases() ) 473 { 474 List<MojoExecution> forkedExecutions = lifecycleMappings.get( phase.getId() ); 475 476 if ( forkedExecutions != null ) 477 { 478 for ( Execution execution : phase.getExecutions() ) 479 { 480 for ( String goal : execution.getGoals() ) 481 { 482 MojoDescriptor forkedMojoDescriptor; 483 484 if ( goal.indexOf( ':' ) < 0 ) 485 { 486 forkedMojoDescriptor = pluginDescriptor.getMojo( goal ); 487 if ( forkedMojoDescriptor == null ) 488 { 489 throw new MojoNotFoundException( goal, pluginDescriptor ); 490 } 491 } 492 else 493 { 494 forkedMojoDescriptor = mojoDescriptorCreator.getMojoDescriptor( goal, session, project ); 495 } 496 497 MojoExecution forkedExecution = 498 new MojoExecution( forkedMojoDescriptor, mojoExecution.getExecutionId() ); 499 500 Xpp3Dom forkedConfiguration = (Xpp3Dom) execution.getConfiguration(); 501 502 forkedExecution.setConfiguration( forkedConfiguration ); 503 504 mojoExecutionConfigurator( forkedExecution ).configure( project, forkedExecution, true ); 505 506 forkedExecutions.add( forkedExecution ); 507 } 508 } 509 510 Xpp3Dom phaseConfiguration = (Xpp3Dom) phase.getConfiguration(); 511 512 if ( phaseConfiguration != null ) 513 { 514 for ( MojoExecution forkedExecution : forkedExecutions ) 515 { 516 Xpp3Dom forkedConfiguration = forkedExecution.getConfiguration(); 517 518 forkedConfiguration = Xpp3Dom.mergeXpp3Dom( phaseConfiguration, forkedConfiguration ); 519 520 forkedExecution.setConfiguration( forkedConfiguration ); 521 } 522 } 523 } 524 } 525 } 526 527 // org.apache.maven.plugins:maven-remote-resources-plugin:1.0:process 528 // TODO: take repo mans into account as one may be aggregating prefixes of many 529 // TODO: collect at the root of the repository, read the one at the root, and fetch remote if something is missing 530 // or the user forces the issue 531 532 private List<MojoExecution> calculateForkedGoal( MojoExecution mojoExecution, MavenSession session, 533 MavenProject project, 534 Collection<MojoDescriptor> alreadyForkedExecutions ) 535 throws MojoNotFoundException, PluginNotFoundException, PluginResolutionException, 536 PluginDescriptorParsingException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException, 537 LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException 538 { 539 MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor(); 540 541 PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor(); 542 543 String forkedGoal = mojoDescriptor.getExecuteGoal(); 544 545 MojoDescriptor forkedMojoDescriptor = pluginDescriptor.getMojo( forkedGoal ); 546 if ( forkedMojoDescriptor == null ) 547 { 548 throw new MojoNotFoundException( forkedGoal, pluginDescriptor ); 549 } 550 551 if ( alreadyForkedExecutions.contains( forkedMojoDescriptor ) ) 552 { 553 return Collections.emptyList(); 554 } 555 556 MojoExecution forkedExecution = new MojoExecution( forkedMojoDescriptor, forkedGoal ); 557 558 mojoExecutionConfigurator( forkedExecution ).configure( project, forkedExecution, true ); 559 560 finalizeMojoConfiguration( forkedExecution ); 561 562 calculateForkedExecutions( forkedExecution, session, project, alreadyForkedExecutions ); 563 564 return Collections.singletonList( forkedExecution ); 565 } 566 567 private MojoExecutionConfigurator mojoExecutionConfigurator( MojoExecution mojoExecution ) 568 { 569 String configuratorId = mojoExecution.getMojoDescriptor().getComponentConfigurator(); 570 if ( configuratorId == null ) 571 { 572 configuratorId = "default"; 573 } 574 575 MojoExecutionConfigurator mojoExecutionConfigurator = mojoExecutionConfigurators.get( configuratorId ); 576 577 if ( mojoExecutionConfigurator == null ) 578 { 579 // 580 // The plugin has a custom component configurator but does not have a custom mojo execution configurator 581 // so fall back to the default mojo execution configurator. 582 // 583 mojoExecutionConfigurator = mojoExecutionConfigurators.get( "default" ); 584 } 585 return mojoExecutionConfigurator; 586 } 587}