Coverage Report - org.apache.maven.plugin.war.util.WebappStructure
 
Classes in this File Line Coverage Branch Coverage Complexity
WebappStructure
90%
137/152
74%
64/86
2,469
WebappStructure$DependenciesAnalysisCallback
N/A
N/A
2,469
WebappStructure$RegistrationCallback
N/A
N/A
2,469
 
 1  
 package org.apache.maven.plugin.war.util;
 2  
 
 3  
 /*
 4  
  * Licensed to the Apache Software Foundation (ASF) under one
 5  
  * or more contributor license agreements.  See the NOTICE file
 6  
  * distributed with this work for additional information
 7  
  * regarding copyright ownership.  The ASF licenses this file
 8  
  * to you under the Apache License, Version 2.0 (the
 9  
  * "License"); you may not use this file except in compliance
 10  
  * with the License.  You may obtain a copy of the License at
 11  
  *
 12  
  *   http://www.apache.org/licenses/LICENSE-2.0
 13  
  *
 14  
  * Unless required by applicable law or agreed to in writing,
 15  
  * software distributed under the License is distributed on an
 16  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 17  
  * KIND, either express or implied.  See the License for the
 18  
  * specific language governing permissions and limitations
 19  
  * under the License.
 20  
  */
 21  
 
 22  
 import org.apache.maven.artifact.Artifact;
 23  
 import org.apache.maven.model.Dependency;
 24  
 import org.codehaus.plexus.util.StringUtils;
 25  
 
 26  
 import java.io.IOException;
 27  
 import java.util.ArrayList;
 28  
 import java.util.Collections;
 29  
 import java.util.HashMap;
 30  
 import java.util.Iterator;
 31  
 import java.util.List;
 32  
 import java.util.Map;
 33  
 import java.util.Set;
 34  
 
 35  
 /**
 36  
  * Represents the structure of a web application composed of multiple
 37  
  * overlays. Each overlay is registered within this structure with the
 38  
  * set of files it holds.
 39  
  * <p/>
 40  
  * Note that this structure is persisted to disk at each invocation to
 41  
  * store which owner holds which path (file).
 42  
  *
 43  
  * @author Stephane Nicoll
 44  
  * @version $Id: WebappStructure.java 1006058 2010-10-08 22:43:15Z dennisl $
 45  
  */
 46  
 public class WebappStructure
 47  
 {
 48  
 
 49  
     private Map registeredFiles;
 50  
 
 51  
     private List dependenciesInfo;
 52  
 
 53  122
     private transient PathSet allFiles = new PathSet();
 54  
 
 55  
     private transient WebappStructure cache;
 56  
 
 57  
     /**
 58  
      * Creates a new empty instance.
 59  
      *
 60  
      * @param dependencies the dependencies of the project
 61  
      */
 62  
     public WebappStructure( List dependencies )
 63  59
     {
 64  59
         this.dependenciesInfo = createDependenciesInfoList( dependencies );
 65  59
         this.registeredFiles = new HashMap();
 66  59
         this.cache = null;
 67  
 
 68  59
     }
 69  
 
 70  
     /**
 71  
      * Creates a new instance with the specified cache.
 72  
      *
 73  
      * @param dependencies the dependencies of the project
 74  
      * @param cache        the cache
 75  
      */
 76  
     public WebappStructure( List dependencies, WebappStructure cache )
 77  63
     {
 78  63
         this.dependenciesInfo = createDependenciesInfoList( dependencies );
 79  63
         this.registeredFiles = new HashMap();
 80  63
         if ( cache == null )
 81  
         {
 82  52
             this.cache = new WebappStructure( dependencies );
 83  
 
 84  
         }
 85  
         else
 86  
         {
 87  11
             this.cache = cache;
 88  
         }
 89  63
     }
 90  
 
 91  
     /**
 92  
      * Returns the list of {@link DependencyInfo} for the project.
 93  
      *
 94  
      * @return the dependencies information of the project
 95  
      */
 96  
     public List getDependenciesInfo()
 97  
     {
 98  3
         return dependenciesInfo;
 99  
     }
 100  
 
 101  
     /**
 102  
      * Returns the dependencies of the project.
 103  
      *
 104  
      * @return the dependencies of the project
 105  
      */
 106  
     public List getDependencies()
 107  
     {
 108  30
         final List result = new ArrayList();
 109  30
         if ( dependenciesInfo == null )
 110  
         {
 111  0
             return result;
 112  
         }
 113  30
         final Iterator it = dependenciesInfo.iterator();
 114  67
         while ( it.hasNext() )
 115  
         {
 116  37
             DependencyInfo dependencyInfo = (DependencyInfo) it.next();
 117  37
             result.add( dependencyInfo.getDependency() );
 118  37
         }
 119  30
         return result;
 120  
     }
 121  
 
 122  
 
 123  
     /**
 124  
      * Specify if the specified <tt>path</tt> is registered or not.
 125  
      *
 126  
      * @param path the relative path from the webapp root directory
 127  
      * @return true if the path is registered, false otherwise
 128  
      */
 129  
     public boolean isRegistered( String path )
 130  
     {
 131  801
         return getFullStructure().contains( path );
 132  
 
 133  
     }
 134  
 
 135  
     /**
 136  
      * Registers the specified path for the specified owner. Returns <tt>true</tt>
 137  
      * if the path is not already registered, <tt>false</tt> otherwise.
 138  
      *
 139  
      * @param id   the owner of the path
 140  
      * @param path the relative path from the webapp root directory
 141  
      * @return true if the file was registered successfully
 142  
      */
 143  
     public boolean registerFile( String id, String path )
 144  
     {
 145  9
         if ( !isRegistered( path ) )
 146  
         {
 147  8
             doRegister( id, path );
 148  8
             return true;
 149  
         }
 150  
         else
 151  
         {
 152  1
             return false;
 153  
         }
 154  
     }
 155  
 
 156  
     /**
 157  
      * Forces the registration of the specified path for the specified owner. If
 158  
      * the file is not registered yet, a simple registration is performed. If the
 159  
      * file already exists, the owner changes to the specified one.
 160  
      * <p/>
 161  
      * Beware that the semantic of the return boolean is different than the one
 162  
      * from {@link #registerFile(String, String)}; returns <tt>true</tt> if an
 163  
      * owner replacement was made and <tt>false</tt> if the file was simply registered
 164  
      * for the first time.
 165  
      *
 166  
      * @param id   the owner of the path
 167  
      * @param path the relative path from the webapp root directory
 168  
      * @return false if the file did not exist, true if the owner was replaced
 169  
      */
 170  
     public boolean registerFileForced( String id, String path )
 171  
     {
 172  26
         if ( !isRegistered( path ) )
 173  
         {
 174  25
             doRegister( id, path );
 175  25
             return false;
 176  
         }
 177  
         else
 178  
         {
 179  
             // Force the switch to the new owner
 180  1
             getStructure( getOwner( path ) ).remove( path );
 181  1
             getStructure( id ).add( path );
 182  1
             return true;
 183  
         }
 184  
 
 185  
     }
 186  
 
 187  
     /**
 188  
      * Registers the specified path for the specified owner. Invokes
 189  
      * the <tt>callback</tt> with the result of the registration.
 190  
      *
 191  
      * @param id       the owner of the path
 192  
      * @param path     the relative path from the webapp root directory
 193  
      * @param callback the callback to invoke with the result of the registration
 194  
      * @throws IOException if the callback invocation throws an IOException
 195  
      */
 196  
     public void registerFile( String id, String path, RegistrationCallback callback )
 197  
         throws IOException
 198  
     {
 199  
 
 200  
         // If the file is already in the current structure, rejects it with the current owner
 201  357
         if ( isRegistered( path ) )
 202  
         {
 203  115
             callback.refused( id, path, getOwner( path ) );
 204  
         }
 205  
         else
 206  
         {
 207  242
             doRegister( id, path );
 208  
             // This is a new file
 209  242
             if ( cache.getOwner( path ) == null )
 210  
             {
 211  211
                 callback.registered( id, path );
 212  
 
 213  
             } // The file already belonged to this owner
 214  31
             else if ( cache.getOwner( path ).equals( id ) )
 215  
             {
 216  23
                 callback.alreadyRegistered( id, path );
 217  
             } // The file belongs to another owner and it's known currently
 218  8
             else if ( getOwners().contains( cache.getOwner( path ) ) )
 219  
             {
 220  6
                 callback.superseded( id, path, cache.getOwner( path ) );
 221  
             } // The file belongs to another owner and it's unknown
 222  
             else
 223  
             {
 224  2
                 callback.supersededUnknownOwner( id, path, cache.getOwner( path ) );
 225  
             }
 226  
         }
 227  357
     }
 228  
 
 229  
     /**
 230  
      * Returns the owner of the specified <tt>path</tt>. If the file is not
 231  
      * registered, returns <tt>null</tt>
 232  
      *
 233  
      * @param path the relative path from the webapp root directory
 234  
      * @return the owner or <tt>null</tt>.
 235  
      */
 236  
     public String getOwner( String path )
 237  
     {
 238  408
         if ( !isRegistered( path ) )
 239  
         {
 240  211
             return null;
 241  
         }
 242  
         else
 243  
         {
 244  197
             final Iterator it = registeredFiles.keySet().iterator();
 245  541
             while ( it.hasNext() )
 246  
             {
 247  541
                 final String owner = (String) it.next();
 248  541
                 final PathSet structure = getStructure( owner );
 249  541
                 if ( structure.contains( path ) )
 250  
                 {
 251  197
                     return owner;
 252  
                 }
 253  
 
 254  344
             }
 255  0
             throw new IllegalStateException(
 256  
                 "Should not happen, path [" + path + "] is flagged as being registered but was not found." );
 257  
         }
 258  
 
 259  
     }
 260  
 
 261  
     /**
 262  
      * Returns the owners. Note that this the returned {@link Set} may be
 263  
      * inconsistent since it represents a persistent cache across multiple
 264  
      * invocations.
 265  
      * <p/>
 266  
      * For instance, if an overlay was removed in this execution, it will be
 267  
      * still be there till the cache is cleaned. This happens when the clean
 268  
      * mojo is invoked.
 269  
      *
 270  
      * @return the list of owners
 271  
      */
 272  
     public Set getOwners()
 273  
     {
 274  8
         return registeredFiles.keySet();
 275  
     }
 276  
 
 277  
     /**
 278  
      * Returns all paths that have been registered so far.
 279  
      *
 280  
      * @return all registered path
 281  
      */
 282  
     public PathSet getFullStructure()
 283  
     {
 284  1076
         return allFiles;
 285  
     }
 286  
 
 287  
     /**
 288  
      * Returns the list of registered files for the specified owner.
 289  
      *
 290  
      * @param id the owner
 291  
      * @return the list of files registered for that owner
 292  
      */
 293  
     public PathSet getStructure( String id )
 294  
     {
 295  972
         PathSet pathSet = (PathSet) registeredFiles.get( id );
 296  972
         if ( pathSet == null )
 297  
         {
 298  96
             pathSet = new PathSet();
 299  96
             registeredFiles.put( id, pathSet );
 300  
         }
 301  972
         return pathSet;
 302  
     }
 303  
 
 304  
 
 305  
     /**
 306  
      * Analyze the dependencies of the project using the specified callback.
 307  
      *
 308  
      * @param callback the callback to use to report the result of the analysis
 309  
      */
 310  
     public void analyseDependencies( DependenciesAnalysisCallback callback )
 311  
     {
 312  15
         if ( callback == null )
 313  
         {
 314  0
             throw new NullPointerException( "Callback could not be null." );
 315  
         }
 316  15
         if ( cache == null )
 317  
         {
 318  
             // Could not analyze dependencies without a cache
 319  0
             return;
 320  
         }
 321  
 
 322  15
         final List currentDependencies = new ArrayList( getDependencies() );
 323  15
         final List previousDependencies = new ArrayList( cache.getDependencies() );
 324  15
         final Iterator it = currentDependencies.listIterator();
 325  33
         while ( it.hasNext() )
 326  
         {
 327  18
             Dependency dependency = (Dependency) it.next();
 328  
             // Check if the dependency is there "as is"
 329  
 
 330  18
             final Dependency matchingDependency = matchDependency( previousDependencies, dependency );
 331  18
             if ( matchingDependency != null )
 332  
             {
 333  14
                 callback.unchangedDependency( dependency );
 334  
                 // Handled so let's remove
 335  14
                 it.remove();
 336  14
                 previousDependencies.remove( matchingDependency );
 337  
             }
 338  
             else
 339  
             {
 340  
                 // Try to get the dependency
 341  4
                 final Dependency previousDep = findDependency( dependency, previousDependencies );
 342  4
                 if ( previousDep == null )
 343  
                 {
 344  2
                     callback.newDependency( dependency );
 345  2
                     it.remove();
 346  
                 }
 347  2
                 else if ( !dependency.getVersion().equals( previousDep.getVersion() ) )
 348  
                 {
 349  1
                     callback.updatedVersion( dependency, previousDep.getVersion() );
 350  1
                     it.remove();
 351  1
                     previousDependencies.remove( previousDep );
 352  
                 }
 353  1
                 else if ( !dependency.getScope().equals( previousDep.getScope() ) )
 354  
                 {
 355  1
                     callback.updatedScope( dependency, previousDep.getScope() );
 356  1
                     it.remove();
 357  1
                     previousDependencies.remove( previousDep );
 358  
                 }
 359  0
                 else if ( dependency.isOptional() != previousDep.isOptional() )
 360  
                 {
 361  0
                     callback.updatedOptionalFlag( dependency, previousDep.isOptional() );
 362  0
                     it.remove();
 363  0
                     previousDependencies.remove( previousDep );
 364  
                 }
 365  
                 else
 366  
                 {
 367  0
                     callback.updatedUnknown( dependency, previousDep );
 368  0
                     it.remove();
 369  0
                     previousDependencies.remove( previousDep );
 370  
                 }
 371  
             }
 372  18
         }
 373  15
         final Iterator previousDepIt = previousDependencies.iterator();
 374  18
         while ( previousDepIt.hasNext() )
 375  
         {
 376  3
             Dependency dependency = (Dependency) previousDepIt.next();
 377  3
             callback.removedDependency( dependency );
 378  3
         }
 379  15
     }
 380  
 
 381  
     /**
 382  
      * Registers the target file name for the specified artifact.
 383  
      *
 384  
      * @param artifact       the artifact
 385  
      * @param targetFileName the target file name
 386  
      */
 387  
     public void registerTargetFileName( Artifact artifact, String targetFileName )
 388  
     {
 389  54
         final Iterator it = dependenciesInfo.iterator();
 390  148
         while ( it.hasNext() )
 391  
         {
 392  94
             DependencyInfo dependencyInfo = (DependencyInfo) it.next();
 393  94
             if ( WarUtils.isRelated( artifact, dependencyInfo.getDependency() ) )
 394  
             {
 395  50
                 dependencyInfo.setTargetFileName( targetFileName );
 396  
             }
 397  94
         }
 398  54
     }
 399  
 
 400  
     /**
 401  
      * Returns the cached target file name that matches the specified
 402  
      * dependency, that is the target file name of the previous run.
 403  
      * <p/>
 404  
      * The dependency object may have changed so the comparison is
 405  
      * based on basic attributes of the dependency.
 406  
      *
 407  
      * @param dependency a dependency
 408  
      * @return the target file name of the last run for this dependency
 409  
      */
 410  
     public String getCachedTargetFileName( Dependency dependency )
 411  
     {
 412  3
         if ( cache == null )
 413  
         {
 414  0
             return null;
 415  
         }
 416  3
         final Iterator it = cache.getDependenciesInfo().iterator();
 417  3
         while ( it.hasNext() )
 418  
         {
 419  3
             DependencyInfo dependencyInfo = (DependencyInfo) it.next();
 420  3
             final Dependency dependency2 = dependencyInfo.getDependency();
 421  3
             if ( StringUtils.equals( dependency.getGroupId(), dependency2.getGroupId() )
 422  
                 && StringUtils.equals( dependency.getArtifactId(), dependency2.getArtifactId() )
 423  
                 && StringUtils.equals( dependency.getType(), dependency2.getType() )
 424  
                 && StringUtils.equals( dependency.getClassifier(), dependency2.getClassifier() ) )
 425  
             {
 426  
 
 427  3
                 return dependencyInfo.getTargetFileName();
 428  
 
 429  
             }
 430  0
         }
 431  0
         return null;
 432  
     }
 433  
 
 434  
     // Private helpers
 435  
 
 436  
     private void doRegister( String id, String path )
 437  
     {
 438  275
         getFullStructure().add( path );
 439  275
         getStructure( id ).add( path );
 440  275
     }
 441  
 
 442  
     /**
 443  
      * Find a dependency that is similar from the specified dependency.
 444  
      *
 445  
      * @param dependency   the dependency to find
 446  
      * @param dependencies a list of dependencies
 447  
      * @return a similar dependency or <tt>null</tt> if no similar dependency is found
 448  
      */
 449  
     private Dependency findDependency( Dependency dependency, List dependencies )
 450  
     {
 451  4
         final Iterator it = dependencies.iterator();
 452  5
         while ( it.hasNext() )
 453  
         {
 454  3
             Dependency dep = (Dependency) it.next();
 455  3
             if ( dependency.getGroupId().equals( dep.getGroupId() )
 456  
                 && dependency.getArtifactId().equals( dep.getArtifactId() )
 457  
                 && dependency.getType().equals( dep.getType() )
 458  
                 && ( ( dependency.getClassifier() == null && dep.getClassifier() == null )
 459  
                     || ( dependency.getClassifier() != null
 460  
                         && dependency.getClassifier().equals( dep.getClassifier() ) ) ) )
 461  
             {
 462  2
                 return dep;
 463  
             }
 464  1
         }
 465  2
         return null;
 466  
     }
 467  
 
 468  
     private Dependency matchDependency( List dependencies, Dependency dependency )
 469  
     {
 470  18
         final Iterator it = dependencies.iterator();
 471  22
         while ( it.hasNext() )
 472  
         {
 473  18
             Dependency dep = (Dependency) it.next();
 474  18
             if ( WarUtils.dependencyEquals( dep, dependency ) )
 475  
             {
 476  14
                 return dep;
 477  
             }
 478  
 
 479  4
         }
 480  4
         return null;
 481  
     }
 482  
 
 483  
 
 484  
     private List createDependenciesInfoList( List dependencies )
 485  
     {
 486  122
         if ( dependencies == null )
 487  
         {
 488  0
             return Collections.EMPTY_LIST;
 489  
         }
 490  122
         final List result = new ArrayList();
 491  122
         final Iterator it = dependencies.iterator();
 492  220
         while ( it.hasNext() )
 493  
         {
 494  98
             Dependency dependency = (Dependency) it.next();
 495  98
             result.add( new DependencyInfo( dependency ) );
 496  98
         }
 497  122
         return result;
 498  
     }
 499  
 
 500  
 
 501  
     private Object readResolve()
 502  
     {
 503  
         // the full structure should be resolved so let's rebuild it
 504  8
         this.allFiles = new PathSet();
 505  8
         final Iterator it = registeredFiles.values().iterator();
 506  23
         while ( it.hasNext() )
 507  
         {
 508  15
             PathSet pathSet = (PathSet) it.next();
 509  15
             this.allFiles.addAll( pathSet );
 510  15
         }
 511  8
         return this;
 512  
     }
 513  
 
 514  
     /**
 515  
      * Callback interface to handle events related to filepath registration in
 516  
      * the webapp.
 517  
      */
 518  
     public interface RegistrationCallback
 519  
     {
 520  
 
 521  
 
 522  
         /**
 523  
          * Called if the <tt>targetFilename</tt> for the specified <tt>ownerId</tt>
 524  
          * has been registered successfully.
 525  
          * <p/>
 526  
          * This means that the <tt>targetFilename</tt> was unknown and has been
 527  
          * registered successfully.
 528  
          *
 529  
          * @param ownerId        the ownerId
 530  
          * @param targetFilename the relative path according to the root of the webapp
 531  
          * @throws IOException if an error occurred while handling this event
 532  
          */
 533  
         void registered( String ownerId, String targetFilename )
 534  
             throws IOException;
 535  
 
 536  
         /**
 537  
          * Called if the <tt>targetFilename</tt> for the specified <tt>ownerId</tt>
 538  
          * has already been registered.
 539  
          * <p/>
 540  
          * This means that the <tt>targetFilename</tt> was known and belongs to the
 541  
          * specified owner.
 542  
          *
 543  
          * @param ownerId        the ownerId
 544  
          * @param targetFilename the relative path according to the root of the webapp
 545  
          * @throws IOException if an error occurred while handling this event
 546  
          */
 547  
         void alreadyRegistered( String ownerId, String targetFilename )
 548  
             throws IOException;
 549  
 
 550  
         /**
 551  
          * Called if the registration of the <tt>targetFilename</tt> for the
 552  
          * specified <tt>ownerId</tt> has been refused since the path already
 553  
          * belongs to the <tt>actualOwnerId</tt>.
 554  
          * <p/>
 555  
          * This means that the <tt>targetFilename</tt> was known and does not
 556  
          * belong to the specified owner.
 557  
          *
 558  
          * @param ownerId        the ownerId
 559  
          * @param targetFilename the relative path according to the root of the webapp
 560  
          * @param actualOwnerId  the actual owner
 561  
          * @throws IOException if an error occurred while handling this event
 562  
          */
 563  
         void refused( String ownerId, String targetFilename, String actualOwnerId )
 564  
             throws IOException;
 565  
 
 566  
         /**
 567  
          * Called if the <tt>targetFilename</tt> for the specified <tt>ownerId</tt>
 568  
          * has been registered successfully by superseding a <tt>deprecatedOwnerId</tt>,
 569  
          * that is the previous owner of the file.
 570  
          * <p/>
 571  
          * This means that the <tt>targetFilename</tt> was known but for another
 572  
          * owner. This usually happens after a project's configuration change. As a
 573  
          * result, the file has been registered successfully to the new owner.
 574  
          *
 575  
          * @param ownerId           the ownerId
 576  
          * @param targetFilename    the relative path according to the root of the webapp
 577  
          * @param deprecatedOwnerId the previous owner that does not exist anymore
 578  
          * @throws IOException if an error occurred while handling this event
 579  
          */
 580  
         void superseded( String ownerId, String targetFilename, String deprecatedOwnerId )
 581  
             throws IOException;
 582  
 
 583  
         /**
 584  
          * Called if the <tt>targetFilename</tt> for the specified <tt>ownerId</tt>
 585  
          * has been registered successfully by superseding a <tt>unknownOwnerId</tt>,
 586  
          * that is an owner that does not exist anymore in the current project.
 587  
          * <p/>
 588  
          * This means that the <tt>targetFilename</tt> was known but for an owner that
 589  
          * does not exist anymore. Hence the file has been registered successfully to
 590  
          * the new owner.
 591  
          *
 592  
          * @param ownerId        the ownerId
 593  
          * @param targetFilename the relative path according to the root of the webapp
 594  
          * @param unknownOwnerId the previous owner that does not exist anymore
 595  
          * @throws IOException if an error occurred while handling this event
 596  
          */
 597  
         void supersededUnknownOwner( String ownerId, String targetFilename, String unknownOwnerId )
 598  
             throws IOException;
 599  
     }
 600  
 
 601  
     /**
 602  
      * Callback interface to handle events related to dependencies analysis.
 603  
      */
 604  
     public interface DependenciesAnalysisCallback
 605  
     {
 606  
 
 607  
         /**
 608  
          * Called if the dependency has not changed since the last build.
 609  
          *
 610  
          * @param dependency the dependency that hasn't changed
 611  
          */
 612  
         void unchangedDependency( Dependency dependency );
 613  
 
 614  
         /**
 615  
          * Called if a new dependency has been added since the last build.
 616  
          *
 617  
          * @param dependency the new dependency
 618  
          */
 619  
         void newDependency( Dependency dependency );
 620  
 
 621  
         /**
 622  
          * Called if the dependency has been removed since the last build.
 623  
          *
 624  
          * @param dependency the dependency that has been removed
 625  
          */
 626  
         void removedDependency( Dependency dependency );
 627  
 
 628  
         /**
 629  
          * Called if the version of the dependency has changed since the last build.
 630  
          *
 631  
          * @param dependency      the dependency
 632  
          * @param previousVersion the previous version of the dependency
 633  
          */
 634  
         void updatedVersion( Dependency dependency, String previousVersion );
 635  
 
 636  
         /**
 637  
          * Called if the scope of the dependency has changed since the last build.
 638  
          *
 639  
          * @param dependency    the dependency
 640  
          * @param previousScope the previous scope
 641  
          */
 642  
         void updatedScope( Dependency dependency, String previousScope );
 643  
 
 644  
         /**
 645  
          * Called if the optional flag of the dependency has changed since the
 646  
          * last build.
 647  
          *
 648  
          * @param dependency       the dependency
 649  
          * @param previousOptional the previous optional flag
 650  
          */
 651  
         void updatedOptionalFlag( Dependency dependency, boolean previousOptional );
 652  
 
 653  
         /**
 654  
          * Called if the dependency has been updated for unknown reason.
 655  
          *
 656  
          * @param dependency  the dependency
 657  
          * @param previousDep the previous dependency
 658  
          */
 659  
         void updatedUnknown( Dependency dependency, Dependency previousDep );
 660  
 
 661  
     }
 662  
 }