1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugin.eclipse.it;
20  
21  import java.io.BufferedReader;
22  import java.io.File;
23  import java.io.FileReader;
24  import java.io.IOException;
25  import java.io.StringReader;
26  import java.net.MalformedURLException;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.HashMap;
30  import java.util.Iterator;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Properties;
34  import java.util.Map.Entry;
35  
36  import junit.framework.AssertionFailedError;
37  
38  import org.apache.maven.artifact.Artifact;
39  import org.apache.maven.artifact.factory.ArtifactFactory;
40  import org.apache.maven.artifact.factory.DefaultArtifactFactory;
41  import org.apache.maven.artifact.handler.DefaultArtifactHandler;
42  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
43  import org.apache.maven.artifact.handler.manager.DefaultArtifactHandlerManager;
44  import org.apache.maven.artifact.repository.ArtifactRepository;
45  import org.apache.maven.artifact.repository.DefaultArtifactRepository;
46  import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
47  import org.apache.maven.plugin.MojoExecutionException;
48  import org.apache.maven.plugin.eclipse.ExecutionFailedException;
49  import org.apache.maven.plugin.eclipse.Messages;
50  import org.apache.maven.plugin.ide.IdeUtils;
51  import org.apache.maven.plugin.testing.AbstractMojoTestCase;
52  import org.apache.maven.project.MavenProject;
53  import org.apache.maven.shared.invoker.InvocationRequest;
54  import org.apache.maven.shared.invoker.InvocationResult;
55  import org.apache.maven.shared.test.plugin.BuildTool;
56  import org.apache.maven.shared.test.plugin.PluginTestTool;
57  import org.apache.maven.shared.test.plugin.ProjectTool;
58  import org.apache.maven.shared.test.plugin.RepositoryTool;
59  import org.apache.maven.shared.test.plugin.TestToolsException;
60  import org.codehaus.classworlds.ClassRealm;
61  import org.codehaus.plexus.PlexusContainer;
62  import org.codehaus.plexus.util.FileUtils;
63  import org.codehaus.plexus.util.IOUtil;
64  import org.codehaus.plexus.util.StringUtils;
65  import org.custommonkey.xmlunit.XMLAssert;
66  import org.custommonkey.xmlunit.XMLUnit;
67  import org.xml.sax.EntityResolver;
68  import org.xml.sax.InputSource;
69  import org.xml.sax.SAXException;
70  
71  /**
72   * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
73   * @author <a href="mailto:fgiust@apache.org">Fabrizio Giustina</a>
74   * @version $Id: AbstractEclipsePluginIT.java 826985 2009-10-20 07:49:24Z nicolas $
75   */
76  public abstract class AbstractEclipsePluginIT
77      extends AbstractMojoTestCase
78  {
79  
80      private BuildTool buildTool;
81  
82      private ProjectTool projectTool;
83  
84      private RepositoryTool repositoryTool;
85  
86      /**
87       * Test repository directory.
88       */
89      protected static File localRepositoryDirectory = getTestFile( "target/test-classes/m2repo" );
90  
91      /**
92       * Pom File
93       */
94      protected static File PomFile = new File( getBasedir(), "pom.xml" );
95  
96      /**
97       * Group-Id for running test builds.
98       */
99      protected static final String GROUP_ID = "org.apache.maven.plugins";
100 
101     /**
102      * Artifact-Id for running test builds.
103      */
104     protected static final String ARTIFACT_ID = "maven-eclipse-plugin";
105 
106     /**
107      * Version under which the plugin was installed to the test-time local repository for running test builds.
108      */
109     protected static final String VERSION = "test";
110 
111     private static final String BUILD_OUTPUT_DIRECTORY = "target/surefire-reports/build-output";
112 
113     private static boolean installed = false;
114 
115     /**
116      * The name of the directory used for comparison of expected output.
117      */
118     private static final String EXPECTED_DIRECTORY_NAME = "expected";
119 
120     /**
121      * The XML Header used to check if the file contains XML content.
122      */
123     private static final String XML_HEADER = "<?xml";
124 
125     /**
126      * @see org.codehaus.plexus.PlexusTestCase#setUp()
127      */
128     protected void setUp()
129         throws Exception
130     {
131         if ( !installed )
132         {
133             System.out.println( "*** Running test builds; output will be directed to: " + BUILD_OUTPUT_DIRECTORY + "\n" );
134         }
135 
136         super.setUp();
137 
138         buildTool = (BuildTool) lookup( BuildTool.ROLE, "default" );
139 
140         projectTool = (ProjectTool) lookup( ProjectTool.ROLE, "default" );
141 
142         repositoryTool = (RepositoryTool) lookup( RepositoryTool.ROLE, "default" );
143 
144         String mavenHome = System.getProperty( "maven.home" );
145 
146         // maven.home is set by surefire when the test is run with maven, but better make the test
147         // run in IDEs without the need of additional properties
148         if ( mavenHome == null )
149         {
150             String path = System.getProperty( "java.library.path" );
151             String[] paths = StringUtils.split( path, System.getProperty( "path.separator" ) );
152             for ( int j = 0; j < paths.length; j++ )
153             {
154                 String pt = paths[j];
155                 if ( new File( pt, "mvn" ).exists() )
156                 {
157                     System.setProperty( "maven.home", new File( pt ).getAbsoluteFile().getParent() );
158                     break;
159                 }
160 
161             }
162         }
163 
164         System.setProperty( "MAVEN_TERMINATE_CMD", "on" );
165 
166         synchronized ( AbstractEclipsePluginIT.class )
167         {
168             if ( !installed )
169             {
170                 PluginTestTool pluginTestTool = (PluginTestTool) lookup( PluginTestTool.ROLE, "default" );
171 
172                 localRepositoryDirectory =
173                     pluginTestTool.preparePluginForUnitTestingWithMavenBuilds( PomFile, "test",
174                                                                                localRepositoryDirectory );
175 
176                 System.out.println( "*** Installed test-version of the Eclipse plugin to: " + localRepositoryDirectory
177                     + "\n" );
178 
179                 // Hack: to work around proxys and DTDs retrievals.
180                 EntityResolver ignoreDtds = new EntityResolver()
181                 {
182 
183                     public InputSource resolveEntity( String publicId, String systemId )
184                         throws SAXException, IOException
185                     {
186                         return new InputSource( new StringReader( "<!ELEMENT ignored (#PCDATA)>" ) );
187                     }
188 
189                 };
190                 XMLUnit.setTestEntityResolver( ignoreDtds );
191                 XMLUnit.setControlEntityResolver( ignoreDtds );
192 
193                 installed = true;
194             }
195         }
196 
197     }
198 
199     /**
200      * @see org.codehaus.plexus.PlexusTestCase#tearDown()
201      */
202     protected void tearDown()
203         throws Exception
204     {
205         super.tearDown();
206 
207         List containers = new ArrayList();
208 
209         containers.add( getContainer() );
210 
211         for ( Iterator iter = containers.iterator(); iter.hasNext(); )
212         {
213             PlexusContainer container = (PlexusContainer) iter.next();
214 
215             if ( container != null )
216             {
217                 container.dispose();
218 
219                 ClassRealm realm = container.getContainerRealm();
220 
221                 if ( realm != null )
222                 {
223                     realm.getWorld().disposeRealm( realm.getId() );
224                 }
225             }
226         }
227     }
228 
229     /**
230      * Execute the eclipse:eclipse goal on a test project and verify generated files.
231      *
232      * @param projectName project directory
233      * @throws Exception any exception generated during test
234      */
235     protected void testProject( String projectName )
236         throws Exception
237     {
238         testProject( projectName, new Properties(), "clean", "eclipse" );
239     }
240 
241     /**
242      * Execute the eclipse:eclipse goal on a test project and verify generated files.
243      *
244      * @param basedir basedir of mvn execution
245      * @throws Exception any exception generated during test
246      */
247     protected void testProject( File basedir )
248         throws Exception
249     {
250         testProject( basedir, new Properties(), "clean", "eclipse" );
251     }
252 
253     /**
254      * Execute the eclipse:eclipse goal on a test project and verify generated files.
255      *
256      * @param projectName project directory
257      * @param properties additional properties
258      * @param cleanGoal TODO
259      * @param genGoal TODO
260      * @throws Exception any exception generated during test
261      */
262     protected void testProject( String projectName, Properties properties, String cleanGoal, String genGoal )
263         throws Exception
264     {
265         testProject( projectName, properties, cleanGoal, genGoal, false );
266     }
267 
268     /**
269      * Execute the eclipse:eclipse goal on a test project and verify generated files.
270      *
271      * @param projectName project directory
272      * @param properties additional properties
273      * @param cleanGoal TODO
274      * @param genGoal TODO
275      * @param withInstall true to include the install goal, false to exclude it.
276      * @throws Exception any exception generated during test
277      */
278     protected void testProject( String projectName, Properties properties, String cleanGoal, String genGoal,
279                                 boolean withInstall )
280         throws Exception
281     {
282         File basedir = getTestFile( "target/test-classes/projects/" + projectName );
283         testProject( basedir, properties, cleanGoal, genGoal, withInstall );
284     }
285 
286     /**
287      * @param basedir Execute the eclipse:eclipse goal on a test project and verify generated files.
288      * @param properties additional properties
289      * @param cleanGoal TODO
290      * @param genGoal TODO
291      * @throws Exception any exception generated during test
292      */
293     protected void testProject( File basedir, Properties properties, String cleanGoal, String genGoal )
294         throws Exception
295     {
296         testProject( basedir, properties, cleanGoal, genGoal, false );
297     }
298 
299     /**
300      * Execute the eclipse:eclipse goal on a test project and verify generated files.
301      *
302      * @param basedir basedir of mvn execution
303      * @param properties additional properties
304      * @param cleanGoal TODO
305      * @param genGoal TODO
306      * @param withInstall true to include the install goal, false to exclude it.
307      * @throws Exception any exception generated during test
308      */
309     protected void testProject( File basedir, Properties properties, String cleanGoal, String genGoal,
310                                 boolean withInstall )
311         throws Exception
312     {
313         File pom = new File( basedir, "pom.xml" );
314 
315         String pluginSpec = getPluginCLISpecification();
316 
317         List goals = new ArrayList();
318 
319         goals.add( pluginSpec + cleanGoal );
320         goals.add( pluginSpec + genGoal );
321         if ( withInstall )
322         {
323             goals.add( "install" );
324         }
325 
326         executeMaven( pom, properties, goals );
327 
328         MavenProject project = readProject( pom );
329 
330         String outputDirPath =
331             IdeUtils.getPluginSetting( project, "org.apache.maven.plugins:maven-eclipse-plugin", "outputDir", null );
332         File projectOutputDir = basedir;
333 
334         if ( outputDirPath != null )
335         {
336             File outputDir = new File( basedir, outputDirPath );
337             outputDir.mkdirs();
338             projectOutputDir = new File( outputDir, project.getArtifactId() );
339         }
340 
341         compareDirectoryContent( basedir, projectOutputDir );
342     }
343 
344     /**
345      * Execute the eclipse:configure-workspace goal on a test project and verify generated files.
346      *
347      * @param projectName project directory
348      * @throws Exception any exception generated during test
349      */
350     protected void testWorkspace( String projectName )
351         throws Exception
352     {
353         testWorkspace( projectName, new Properties(), "configure-workspace" );
354     }
355 
356     /**
357      * Execute the eclipse:configure-workspace goal on a test project and verify generated files.
358      *
359      * @param projectName project directory
360      * @throws Exception any exception generated during test
361      */
362     protected void testWorkspace( String projectName, String goal )
363         throws Exception
364     {
365         testWorkspace( projectName, new Properties(), goal );
366     }
367 
368     /**
369      * Execute the eclipse:configure-workspace goal on a test project and verify generated files.
370      *
371      * @param projectName project directory
372      * @param properties additional properties
373      * @param cleanGoal TODO
374      * @param genGoal TODO
375      * @throws Exception any exception generated during test
376      */
377     protected void testWorkspace( String projectName, Properties properties, String genGoal )
378         throws Exception
379     {
380         File basedir = getOutputDirectory( projectName );
381 
382         File pom = new File( basedir, "pom.xml" );
383 
384         String pluginSpec = getPluginCLISpecification();
385 
386         List goals = new ArrayList();
387 
388         goals.add( pluginSpec + genGoal );
389 
390         executeMaven( pom, properties, goals );
391 
392         MavenProject project = readProject( pom );
393 
394         String outputDirPath =
395             IdeUtils.getPluginSetting( project, "org.apache.maven.plugins:maven-eclipse-plugin", "outputDir", null );
396         File outputDir;
397         File projectOutputDir = basedir;
398 
399         if ( outputDirPath == null )
400         {
401             outputDir = basedir;
402         }
403         else
404         {
405             outputDir = new File( basedir, outputDirPath );
406             outputDir.mkdirs();
407             projectOutputDir = new File( outputDir, project.getArtifactId() );
408         }
409 
410         compareDirectoryContent( basedir, projectOutputDir );
411 
412     }
413 
414     protected File getOutputDirectory( String projectName )
415     {
416         return getTestFile( "target/test-classes/projects/" + projectName );
417     }
418 
419     protected File getTestWorkspaceWorkDirectory( String projectName )
420     {
421         return new File( this.getOutputDirectory( projectName ), ".metadata" );
422     }
423 
424     protected void executeMaven( File pom, Properties properties, List goals )
425         throws TestToolsException, ExecutionFailedException
426     {
427         executeMaven( pom, properties, goals, true );
428     }
429 
430     protected void executeMaven( File pom, Properties properties, List goals, boolean switchLocalRepo )
431         throws TestToolsException, ExecutionFailedException
432     {
433         System.out.println( "  Building " + pom.getParentFile().getName() );
434 
435         new File( BUILD_OUTPUT_DIRECTORY ).mkdirs();
436 
437         NullPointerException npe = new NullPointerException();
438         StackTraceElement[] trace = npe.getStackTrace();
439 
440         File buildLog = null;
441 
442         for ( int i = 0; i < trace.length; i++ )
443         {
444             StackTraceElement element = trace[i];
445 
446             String methodName = element.getMethodName();
447 
448             if ( methodName.startsWith( "test" ) && !methodName.equals( "testProject" ) )
449             {
450                 String classname = element.getClassName();
451 
452                 buildLog = new File( BUILD_OUTPUT_DIRECTORY, classname + "_" + element.getMethodName() + ".build.log" );
453 
454                 break;
455             }
456         }
457 
458         if ( buildLog == null )
459         {
460             buildLog = new File( BUILD_OUTPUT_DIRECTORY, "unknown.build.log" );
461         }
462 
463         if (properties == null) properties = new Properties();
464         InvocationRequest request = buildTool.createBasicInvocationRequest( pom, properties, goals, buildLog );
465         request.setUpdateSnapshots( false );
466         request.setShowErrors( true );
467         request.getProperties().setProperty( "downloadSources", "false" );
468         request.getProperties().setProperty( "downloadJavadocs", "false" );
469 
470         request.setDebug( true );
471 
472         if ( switchLocalRepo )
473         {
474             request.setLocalRepositoryDirectory( localRepositoryDirectory );
475         }
476 
477         InvocationResult result = buildTool.executeMaven( request );
478 
479         if ( result.getExitCode() != 0 )
480         {
481             String buildLogUrl = buildLog.getAbsolutePath();
482 
483             try
484             {
485                 buildLogUrl = buildLog.toURL().toExternalForm();
486             }
487             catch ( MalformedURLException e )
488             {
489             }
490 
491             throw new ExecutionFailedException( "Failed to execute build.\nPOM: " + pom + "\nGoals: "
492                 + StringUtils.join( goals.iterator(), ", " ) + "\nExit Code: " + result.getExitCode() + "\nError: "
493                 + result.getExecutionException() + "\nBuild Log: " + buildLogUrl + "\n", result );
494         }
495     }
496 
497     protected MavenProject readProject( File pom )
498         throws TestToolsException
499     {
500         return projectTool.readProject( pom, localRepositoryDirectory );
501     }
502 
503     protected String getPluginCLISpecification()
504     {
505         String pluginSpec = GROUP_ID + ":" + ARTIFACT_ID + ":";
506 
507         // String pluginVersion = System.getProperty( "pluginVersion" );
508         //
509         // if ( pluginVersion != null )
510         // {
511         // pluginSpec += pluginVersion + ":";
512         // }
513         //
514         // System.out.println( "\n\nUsing Eclipse plugin version: " + pluginVersion + "\n\n" );
515 
516         // try using the test-version installed during setUp()
517         pluginSpec += VERSION + ":";
518 
519         return pluginSpec;
520     }
521 
522     /**
523      * @param basedir the base directory of the project
524      * @param projectOutputDir the directory where the eclipse plugin will write the output files.
525      * @throws MojoExecutionException
526      */
527     protected void compareDirectoryContent( File basedir, File projectOutputDir )
528         throws MojoExecutionException
529     {
530         File[] expectedDirectories = getExpectedDirectories( basedir );
531 
532         for ( int i = 0; i < expectedDirectories.length; i++ )
533         {
534             File expectedDirectory = expectedDirectories[i];
535             File[] expectedFilesToCompare = getExpectedFilesToCompare( expectedDirectory );
536 
537             for ( int j = 0; j < expectedFilesToCompare.length; j++ )
538             {
539                 File expectedFile = expectedFilesToCompare[j];
540                 File actualFile = getActualFile( projectOutputDir, basedir, expectedFile );
541 
542                 if ( !actualFile.exists() )
543                 {
544                     throw new AssertionFailedError( "Expected file not found: " + actualFile.getAbsolutePath() );
545                 }
546 
547                 assertFileEquals( expectedFile, actualFile );
548 
549             }
550         }
551     }
552 
553     protected void assertFileEquals( File expectedFile, File actualFile )
554         throws MojoExecutionException
555     {
556         if ( !actualFile.exists() )
557         {
558             throw new AssertionFailedError( "Expected file not found: " + actualFile.getAbsolutePath() );
559         }
560 
561         HashMap variableReplacement = new HashMap();
562         variableReplacement.put( "${basedir}",
563                                  IdeUtils.fixSeparator( IdeUtils.getCanonicalPath( new File( getBasedir() ) ) ) );
564         variableReplacement.put( "${M2_REPO}",
565                                  IdeUtils.fixSeparator( IdeUtils.getCanonicalPath( localRepositoryDirectory ) ) );
566 
567         String expectedFileContents = preprocess( expectedFile, variableReplacement );
568         String actualFileContents = preprocess( actualFile, null );
569 
570         if ( isXml( expectedFile ) )
571         {
572             assertXmlFileEquals( expectedFile, expectedFileContents, actualFile, actualFileContents );
573         }
574         else
575         {
576             assertTextFileEquals( expectedFile, expectedFileContents, actualFile, actualFileContents );
577         }
578     }
579 
580     /**
581      * Assert that two XML files are equal.
582      *
583      * @param expectedFile the expected file - only used for path information
584      * @param expectedFileContents the contents of the expected file
585      * @param actualFile the actual file - only used for path information
586      * @param actualFileContents the contents of the actual file
587      * @throws MojoExecutionException failures.
588      */
589     private void assertXmlFileEquals( File expectedFile, String expectedFileContents, File actualFile,
590                                       String actualFileContents )
591         throws MojoExecutionException
592     {
593         try
594         {
595             XMLAssert.assertXMLEqual( "Comparing '" + IdeUtils.getCanonicalPath( actualFile ) + "' against '"
596                 + IdeUtils.getCanonicalPath( expectedFile ), expectedFileContents, actualFileContents );
597         }
598         catch ( IOException e )
599         {
600             throw new MojoExecutionException( IdeUtils.getCanonicalPath( expectedFile )
601                 + "assertXmlFileEquals failure: IO " + e.getMessage(), e );
602         }
603         catch ( SAXException e )
604         {
605             throw new MojoExecutionException( "assertXmlFileEquals failure: SAX " + e.getMessage(), e );
606         }
607     }
608 
609     /**
610      * Assert that two text files are equals. Lines that start with # are comments and ignored.
611      *
612      * @param expectedFile the expected file - only used for path information
613      * @param expectedFileContents the contents of the expected file
614      * @param actualFile the actual file - only used for path information
615      * @param actualFileContents the contents of the actual fiel
616      * @throws MojoExecutionException failures.
617      */
618     private void assertTextFileEquals( File expectedFile, String expectedFileContents, File actualFile,
619                                        String actualFileContents )
620         throws MojoExecutionException
621     {
622         List expectedLines = getLines( expectedFileContents );
623         List actualLines = getLines( actualFileContents );
624         for ( int i = 0; i < expectedLines.size(); i++ )
625         {
626             String expected = expectedLines.get( i ).toString();
627             if ( actualLines.size() <= i )
628             {
629                 fail( "Too few lines in the actual file. Was " + actualLines.size() + ", expected: "
630                     + expectedLines.size() );
631             }
632             String actual = actualLines.get( i ).toString();
633             if ( expected.startsWith( "#" ) && actual.startsWith( "#" ) )
634             {
635                 // ignore comments, for settings file
636                 continue;
637             }
638             assertEquals( "Comparing '" + IdeUtils.getCanonicalPath( actualFile ) + "' against '"
639                 + IdeUtils.getCanonicalPath( expectedFile ) + "' at line #" + ( i + 1 ), expected, actual );
640         }
641         assertTrue( "Unequal number of lines.", expectedLines.size() == actualLines.size() );
642     }
643 
644     /**
645      * Preprocess the file so that equals comparison can be done. Preprocessing may vary based on filename.
646      *
647      * @param file the file being processed
648      * @param variables if not null, then replace all keys with the corresponding values in the expected string.
649      * @return processed input
650      */
651     private String preprocess( File file, Map variables )
652         throws MojoExecutionException
653     {
654         String result = null;
655         try
656         {
657             result = FileUtils.fileRead( file, "UTF-8" );
658         }
659         catch ( IOException ex )
660         {
661             throw new MojoExecutionException( "Unable to read file", ex );
662         }
663         result = replaceVariables( result, variables );
664         result = IdeUtils.fixWindowsDriveURI( result );
665 
666         /*
667          * NOTE: This is another hack to compensate for some metadata files that contain a complete XML file as the
668          * value for a key like "org.eclipse.jdt.ui.formatterprofiles" from "org.eclipse.jdt.ui.prefs". Line terminators
669          * in this value are platform-dependent.
670          */
671         if ( file.getName().endsWith( ".prefs" ) )
672         {
673             result = normalizeNewlineTerminators( result );
674         }
675 
676         /*
677          * NOTE: This is a hack to compensate for files that contain generated values like dependent-object in
678          * org.eclipse.wst.common.component. Regex would be a better solution.
679          */
680         if ( file.getName().equals( "org.eclipse.wst.common.component" ) || file.getName().equals( ".modulemaps" )
681             || file.getName().equals( "application.xml" ) )
682         {
683             result = result.replaceAll( "_\\d+", "" );
684         }
685         return result;
686     }
687 
688     /**
689      * Normalize line terminators into \n. \r\n, \r, \n all get changed into \n.
690      *
691      * @param input the string to normalize
692      * @return string with line terminators normalized
693      */
694     private String normalizeNewlineTerminators( String input )
695     {
696         return input.replaceAll( "(\\\\r\\\\n)|(\\\\n)|(\\\\r)", "\\n" );
697     }
698 
699     /**
700      * @param str input string
701      * @param variables map of variables (keys) and replacement value (values)
702      * @return the string with all variable values replaced.
703      */
704     private String replaceVariables( String str, Map variables )
705     {
706         String result = str;
707         if ( variables != null && !variables.isEmpty() )
708         {
709             Iterator iter = variables.entrySet().iterator();
710             while ( iter.hasNext() )
711             {
712                 Map.Entry entry = (Entry) iter.next();
713                 String variable = (String) entry.getKey();
714                 String replacement = (String) entry.getValue();
715                 result = StringUtils.replace( result, variable, replacement );
716             }
717         }
718 
719         return result;
720     }
721 
722     protected void assertContains( String message, String full, String substring )
723     {
724         if ( full == null || full.indexOf( substring ) == -1 )
725         {
726             StringBuffer buf = new StringBuffer();
727             if ( message != null )
728             {
729                 buf.append( message );
730             }
731             buf.append( ". " );
732             buf.append( "Expected \"" );
733             buf.append( substring );
734             buf.append( "\" not found" );
735             fail( buf.toString() );
736         }
737     }
738 
739     protected void assertDoesNotContain( String message, String full, String substring )
740     {
741         if ( full == null || full.indexOf( substring ) != -1 )
742         {
743             StringBuffer buf = new StringBuffer();
744             if ( message != null )
745             {
746                 buf.append( message );
747             }
748             buf.append( ". " );
749             buf.append( "Unexpected \"" );
750             buf.append( substring );
751             buf.append( "\" found" );
752             fail( buf.toString() );
753         }
754     }
755 
756     private List getLines( String input )
757         throws MojoExecutionException
758     {
759         try
760 
761         {
762             List lines = new ArrayList();
763 
764             BufferedReader reader = new BufferedReader( new StringReader( input ) );
765 
766             String line;
767 
768             while ( ( line = reader.readLine() ) != null )
769             {
770                 lines.add( line );
771             }
772 
773             IOUtil.close( reader );
774 
775             return lines;
776         }
777         catch ( IOException e )
778         {
779             throw new MojoExecutionException( "failed to getLines", e );
780         }
781     }
782 
783     /**
784      * @param basedir base directory to search for directories named "expected"
785      * @return an array of directories that match "expected"
786      */
787     private File[] getExpectedDirectories( File basedir )
788     {
789         List expectedDirectories = new ArrayList();
790         List subdirectories = new ArrayList();
791 
792         File[] allFiles = basedir.listFiles();
793         if ( allFiles != null )
794         {
795             for ( int i = 0; i < allFiles.length; i++ )
796             {
797                 File currentFile = allFiles[i];
798                 if ( currentFile.isDirectory() )
799                 {
800                     if ( currentFile.getName().equals( EXPECTED_DIRECTORY_NAME ) )
801                     {
802                         expectedDirectories.add( currentFile );
803                     }
804                     else
805                     {
806                         subdirectories.add( currentFile );
807                     }
808                 }
809             }
810         }
811         if ( !subdirectories.isEmpty() )
812         {
813             for ( Iterator iter = subdirectories.iterator(); iter.hasNext(); )
814             {
815                 File subdirectory = (File) iter.next();
816                 File[] subdirectoryFiles = getExpectedDirectories( subdirectory );
817                 expectedDirectories.addAll( Arrays.asList( subdirectoryFiles ) );
818             }
819         }
820         return (File[]) expectedDirectories.toArray( new File[expectedDirectories.size()] );
821     }
822 
823     /**
824      * @param expectedDirectory the expected directory to locate expected Files
825      * @return an array of Files found under the expectedDirectory - will recurse through the directory structure.
826      */
827     private File[] getExpectedFilesToCompare( File expectedDirectory )
828     {
829         List expectedFiles = new ArrayList();
830         List subdirectories = new ArrayList();
831 
832         File[] allFiles = expectedDirectory.listFiles();
833         if ( allFiles != null )
834         {
835             for ( int i = 0; i < allFiles.length; i++ )
836             {
837                 File currentFile = allFiles[i];
838                 if ( currentFile.isDirectory() )
839                 {
840                     subdirectories.add( currentFile );
841                 }
842                 else
843                 {
844                     expectedFiles.add( currentFile );
845                 }
846             }
847         }
848         if ( !subdirectories.isEmpty() )
849         {
850             for ( Iterator iter = subdirectories.iterator(); iter.hasNext(); )
851             {
852                 File subdirectory = (File) iter.next();
853                 File[] subdirectoryFiles = getExpectedFilesToCompare( subdirectory );
854                 expectedFiles.addAll( Arrays.asList( subdirectoryFiles ) );
855             }
856         }
857 
858         return (File[]) expectedFiles.toArray( new File[expectedFiles.size()] );
859     }
860 
861     /**
862      * Locate the actual file needed for comparison. The expectedFile has the baseDir prefix removed and the resulting
863      * relative path used to locate the file within the projectOutputDir.
864      *
865      * @param projectOutputDir the directory where the eclipse plugin writes files to
866      * @param basedir the base dir of the project being tested
867      * @param expectedFile the expected file used to compare to the actual file
868      * @return the actual file needed for comparison against the expectedFile
869      * @throws MojoExecutionException failures for obtaining actual file.
870      */
871     private File getActualFile( File projectOutputDir, File basedir, File expectedFile )
872         throws MojoExecutionException
873     {
874         String relativePath = IdeUtils.toRelativeAndFixSeparator( basedir, expectedFile, false );
875         relativePath = relativePath.replaceFirst( EXPECTED_DIRECTORY_NAME, "" );
876         File actualFile = new File( projectOutputDir, relativePath );
877         try
878         {
879             return actualFile.getCanonicalFile();
880         }
881         catch ( IOException e )
882         {
883             throw new MojoExecutionException(
884                                               Messages.getString(
885                                                                   "EclipsePlugin.cantcanonicalize", actualFile.getAbsolutePath() ), e ); //$NON-NLS-1$
886         }
887     }
888 
889     /**
890      * Test if the file contains xml content.
891      *
892      * @param f the file to test
893      * @return true if the file contains xml content, false otherwise.
894      */
895     private boolean isXml( File f )
896     {
897         FileReader reader = null;
898         try
899         {
900             reader = new FileReader( f );
901             char[] header = new char[XML_HEADER.length()];
902             reader.read( header );
903             return XML_HEADER.equals( new String( header ) );
904         }
905         catch ( Exception e )
906         {
907             return false;
908         }
909         finally
910         {
911             IOUtil.close( reader );
912         }
913     }
914 
915     /**
916      * Return the not available marker file for the specified artifact details.
917      *
918      * @param groupId group id of artifact
919      * @param artifactId artifact id of artifact
920      * @param version version of artifact
921      * @param classifier the classifier of the artifact
922      * @param inClassifier the sources/javadocs to be attached
923      * @return the not available marker file
924      * @throws Exception failures.
925      * @see IdeUtils#createArtifactWithClassifier(String, String, String, String, String, ArtifactFactory)
926      */
927     protected File getNotAvailableMarkerFile( String groupId, String artifactId, String version, String classifier,
928                                               String inClassifier )
929         throws Exception
930     {
931         // HACK: START
932         // TODO: Work out how to use Plexus to obtain these values
933         String url = "file://" + localRepositoryDirectory;
934         ArtifactRepository localRepository =
935             new DefaultArtifactRepository( "local", url, new DefaultRepositoryLayout() );
936 
937         ArtifactFactory artifactFactory = new DefaultArtifactFactory();
938 
939         DefaultArtifactHandler javaSourceArtifactHandler = new DefaultArtifactHandler( "java-source" );
940         setVariableValueToObject( javaSourceArtifactHandler, "extension", "jar" );
941 
942         DefaultArtifactHandler javadocArtifactHandler = new DefaultArtifactHandler( "javadoc" );
943         setVariableValueToObject( javadocArtifactHandler, "extension", "jar" );
944 
945         Map artifactHandlers = new HashMap();
946         artifactHandlers.put( "java-source", javaSourceArtifactHandler );
947         artifactHandlers.put( "javadoc", javadocArtifactHandler );
948 
949         ArtifactHandlerManager artifactHandlerManager = new DefaultArtifactHandlerManager();
950         setVariableValueToObject( artifactHandlerManager, "artifactHandlers", artifactHandlers );
951         setVariableValueToObject( artifactFactory, "artifactHandlerManager", artifactHandlerManager );
952         // HACK: END
953 
954         Artifact artifact =
955             IdeUtils.createArtifactWithClassifier( groupId, artifactId, version, classifier, inClassifier,
956                                                    artifactFactory );
957         return IdeUtils.getNotAvailableMarkerFile( localRepository, artifact );
958     }
959 
960     /**
961      * Assert that the not available marker file exists for the specified artifact details.
962      *
963      * @param groupId group id of artifact
964      * @param artifactId artifact id of artifact
965      * @param version version of artifact
966      * @param classifier the classifier of the artifact
967      * @param inClassifier the sources/javadocs to be attached
968      * @throws Exception failures
969      */
970     protected void assertNotAvailableMarkerFileExists( String groupId, String artifactId, String version,
971                                                        String classifier, String inClassifier )
972         throws Exception
973     {
974         File markerFile = getNotAvailableMarkerFile( groupId, artifactId, version, classifier, inClassifier );
975         assertTrue( "The \"Not Available\" marker file does not exist: " + markerFile, markerFile.exists() );
976     }
977 
978     /**
979      * Assert that the not available marker file does not exist for the specified artifact details.
980      *
981      * @param groupId group id of artifact
982      * @param artifactId artifact id of artifact
983      * @param version version of artifact
984      * @param classifier the classifier of the artifact
985      * @param inClassifier the sources/javadocs to be attached
986      * @throws Exception failures
987      */
988     protected void assertNotAvailableMarkerFileDoesNotExist( String groupId, String artifactId, String version,
989                                                              String classifier, String inClassifier )
990         throws Exception
991     {
992         File markerFile = getNotAvailableMarkerFile( groupId, artifactId, version, classifier, inClassifier );
993         assertTrue( "The \"Not Available\" marker file incorrectly exists: " + markerFile, !markerFile.exists() );
994     }
995 
996 }