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