PageRenderTime 41ms CodeModel.GetById 9ms RepoModel.GetById 1ms app.codeStats 0ms

/groovy-eclipse/org.codehaus.groovy.eclipse.core/src/org/codehaus/groovy/eclipse/core/model/GroovyProject.java

http://groovy-eclipse.googlecode.com/
Java | 1249 lines | 1164 code | 8 blank | 77 comment | 31 complexity | 10397917b7e3cae305ec81e836049c36 MD5 | raw file
Possible License(s): Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. /*
  2. * Created on 21-Jan-2004
  3. *
  4. * To change the template for this generated file go to Window - Preferences -
  5. * Java - Code Generation - Code and Comments
  6. */
  7. package org.codehaus.groovy.eclipse.core.model;
  8. import static org.apache.commons.io.FileUtils.forceDelete;
  9. import static org.apache.commons.io.IOUtils.closeQuietly;
  10. import static org.apache.commons.lang.StringUtils.defaultString;
  11. import static org.apache.commons.lang.StringUtils.isBlank;
  12. import static org.apache.commons.lang.StringUtils.isNotBlank;
  13. import static org.apache.commons.lang.StringUtils.removeStart;
  14. import static org.codehaus.groovy.eclipse.collections.ListUtil.newEmptyList;
  15. import static org.codehaus.groovy.eclipse.collections.ListUtil.newList;
  16. import static org.codehaus.groovy.eclipse.collections.MapUtil.newEmptyLinkedMap;
  17. import static org.codehaus.groovy.eclipse.collections.SetUtil.linkedSet;
  18. import static org.codehaus.groovy.eclipse.core.GroovyCore.logException;
  19. import static org.codehaus.groovy.eclipse.core.GroovyCore.trace;
  20. import static org.codehaus.groovy.eclipse.core.model.GroovyProjectModel.getClassesForModules;
  21. import static org.codehaus.groovy.eclipse.core.model.GroovyProjectModel.getSourceFileKey;
  22. import static org.codehaus.groovy.eclipse.core.preferences.PreferenceConstants.GROOVY_DONT_CHECK_PACKAGE_VS_SRC_PATH;
  23. import static org.codehaus.groovy.eclipse.core.preferences.PreferenceConstants.GROOVY_DONT_GENERATE_CLASS_FILES;
  24. import static org.codehaus.groovy.eclipse.core.util.CoreUtils.createLineToOffsetMapping;
  25. import static org.eclipse.core.resources.IResource.DEPTH_INFINITE;
  26. import static org.eclipse.core.resources.IncrementalProjectBuilder.CLEAN_BUILD;
  27. import static org.eclipse.core.resources.IncrementalProjectBuilder.FULL_BUILD;
  28. import static org.eclipse.core.resources.IncrementalProjectBuilder.INCREMENTAL_BUILD;
  29. import static org.eclipse.core.resources.ResourcesPlugin.getWorkspace;
  30. import static org.eclipse.swt.widgets.Display.getDefault;
  31. import java.io.BufferedReader;
  32. import java.io.ByteArrayInputStream;
  33. import java.io.File;
  34. import java.io.IOException;
  35. import java.io.InputStream;
  36. import java.io.InputStreamReader;
  37. import java.net.MalformedURLException;
  38. import java.net.URL;
  39. import java.net.URLClassLoader;
  40. import java.util.Iterator;
  41. import java.util.List;
  42. import java.util.Map;
  43. import java.util.Set;
  44. import org.apache.commons.lang.StringUtils;
  45. import org.codehaus.groovy.antlr.GroovySourceAST;
  46. import org.codehaus.groovy.ast.ClassNode;
  47. import org.codehaus.groovy.ast.ModuleNode;
  48. import org.codehaus.groovy.eclipse.core.GroovyCore;
  49. import org.codehaus.groovy.eclipse.core.builder.GroovyNature;
  50. import org.codehaus.groovy.eclipse.core.compiler.GroovyCompiler;
  51. import org.codehaus.groovy.eclipse.core.compiler.GroovyCompilerConfigurationBuilder;
  52. import org.codehaus.groovy.eclipse.core.compiler.IGroovyCompilationReporter;
  53. import org.codehaus.groovy.eclipse.core.compiler.IGroovyCompiler;
  54. import org.codehaus.groovy.eclipse.core.compiler.IGroovyCompilerConfiguration;
  55. import org.codehaus.groovy.eclipse.core.preferences.PreferenceConstants;
  56. import org.codehaus.groovy.eclipse.core.preferences.PropertyChangeListener;
  57. import org.codehaus.groovy.syntax.SyntaxException;
  58. import org.eclipse.core.resources.IContainer;
  59. import org.eclipse.core.resources.IFile;
  60. import org.eclipse.core.resources.IFolder;
  61. import org.eclipse.core.resources.IMarker;
  62. import org.eclipse.core.resources.IProject;
  63. import org.eclipse.core.resources.IResource;
  64. import org.eclipse.core.resources.IResourceDelta;
  65. import org.eclipse.core.resources.IWorkspaceRoot;
  66. import org.eclipse.core.resources.IWorkspaceRunnable;
  67. import org.eclipse.core.resources.ProjectScope;
  68. import org.eclipse.core.resources.WorkspaceJob;
  69. import org.eclipse.core.runtime.CoreException;
  70. import org.eclipse.core.runtime.IPath;
  71. import org.eclipse.core.runtime.IProgressMonitor;
  72. import org.eclipse.core.runtime.IStatus;
  73. import org.eclipse.core.runtime.NullProgressMonitor;
  74. import org.eclipse.core.runtime.Path;
  75. import org.eclipse.core.runtime.Status;
  76. import org.eclipse.core.runtime.jobs.ILock;
  77. import org.eclipse.core.runtime.jobs.Job;
  78. import org.eclipse.jdt.core.IClasspathEntry;
  79. import org.eclipse.jdt.core.IJavaProject;
  80. import org.eclipse.jdt.core.IPackageFragmentRoot;
  81. import org.eclipse.jdt.core.JavaCore;
  82. import org.eclipse.jdt.core.JavaModelException;
  83. import org.eclipse.jface.preference.IPersistentPreferenceStore;
  84. import org.eclipse.ui.preferences.ScopedPreferenceStore;
  85. /**
  86. * The main groovy project class used to configure the project settings, and do
  87. * the compiling.
  88. *
  89. * @author MelamedZ
  90. * @author Hein Meling
  91. * @author empovazan - many changes extracting actual building into a
  92. * GroovyCompiler class and updating AST from changed by unsaved source
  93. * files.
  94. */
  95. public class GroovyProject
  96. {
  97. // Tries to get Groovy output path
  98. public static String getProjectOutputPath( final IJavaProject javaProject )
  99. {
  100. final String projectPreference = preferenceStore( javaProject.getProject() ).getString( PreferenceConstants.GROOVY_COMPILER_OUTPUT_PATH );
  101. if( isNotBlank( projectPreference ) )
  102. {
  103. if( !javaProject.getProject().getFolder( projectPreference ).exists() )
  104. {
  105. trace( "Trying to set Groovy output path inside of GroovyProject.getProjectOutputPath()" );
  106. // TODO: emp - again, redundant? vs:
  107. // GroovyModel.getModel().getGroovyProject(javaProject).
  108. // setOutputPath(projectPreference,
  109. // projectPreference);
  110. try
  111. {
  112. GroovyModel.getModel().getGroovyProject( javaProject ).setOutputPath( projectPreference, projectPreference );
  113. }
  114. catch( final Exception e )
  115. {
  116. logException( "Error while retrieving the output path of the project " + javaProject.getElementName(), e );
  117. return null;
  118. }
  119. }
  120. return projectPreference;
  121. }
  122. return "";
  123. }
  124. /**
  125. * Returns the Eclipse project output path
  126. *
  127. * @param project
  128. * @return
  129. * @throws JavaModelException
  130. */
  131. public static String getOutputPath( final IJavaProject project )
  132. throws JavaModelException
  133. {
  134. if( project == null || !project.exists() || project.getProject().getLocation() == null )
  135. return null;
  136. return project.getProject().getLocation().toString() + "/" + getProjectOutputPath( project );
  137. }
  138. public static String getOutputOSPath( final IJavaProject project ) throws JavaModelException
  139. {
  140. if( project == null || !project.exists() || project.getProject().getLocation() == null )
  141. return null;
  142. final String outputPath = project.getProject().getLocation().toOSString()
  143. + File.separator
  144. + getProjectOutputPath( project ).replace( '/', File.separatorChar );
  145. return outputPath;
  146. }
  147. /**
  148. * called when setting the Eclipse Project preference for compiled groovy
  149. * output - create the new folder if it exists - delete the old folder - add
  150. * the new folder to the Project classpath - rebuild the project
  151. *
  152. * @param oldPath
  153. * @param newPath
  154. */
  155. public void setOutputPath( final String oldPath,
  156. final String newPath )
  157. {
  158. trace("in GroovyProject.setOutputPath() - attempting to change output path from " + oldPath + " to " + newPath);
  159. final IWorkspaceRoot root = getWorkspace().getRoot();
  160. final IProject project = javaProject.getProject();
  161. // if the old equals the new and it already exists, nothing to do
  162. if( StringUtils.equals( newPath, oldPath ) && isNotBlank( newPath ) )
  163. {
  164. final IFolder folder = root.getFolder( new Path( project.getFullPath() + "/" + newPath ) );
  165. if( folder.exists() )
  166. return;
  167. }
  168. final IWorkspaceRunnable runnable = new IWorkspaceRunnable()
  169. {
  170. public void run( final IProgressMonitor monitor )
  171. throws CoreException
  172. {
  173. final String savedWorkspacePath = project.getFullPath() + "/" + oldPath;
  174. final IFolder savedFolder = isNotBlank( oldPath )
  175. ? root.getFolder( new Path( savedWorkspacePath ) )
  176. : null;
  177. // delete the old Groovy output folder
  178. if( savedFolder != null && savedFolder.exists() )
  179. {
  180. if( !javaProject.getOutputLocation().equals( savedFolder.getFullPath() ) )
  181. savedFolder.delete( true, monitor );
  182. }
  183. // user folder typed in
  184. if( isNotBlank( newPath ) )
  185. {
  186. final IFolder folder = project.getFolder( newPath );
  187. trace( "new output folder equals:" + folder );
  188. if( !javaProject.getOutputLocation().equals( folder.getFullPath() ) && !folder.exists() )
  189. {
  190. folder.create( true, false, monitor );
  191. folder.setDerived( true );
  192. }
  193. }
  194. return;
  195. }
  196. };
  197. new WorkspaceJob( "Updating Groovy output location for project: " + project.getName() )
  198. {
  199. @Override
  200. public IStatus runInWorkspace( final IProgressMonitor monitor )
  201. throws CoreException
  202. {
  203. getWorkspace().run( runnable, monitor );
  204. return Status.OK_STATUS;
  205. }
  206. }.schedule();
  207. }
  208. class CompilationReporter
  209. implements IGroovyCompilationReporter
  210. {
  211. private final Map< String, IFile > mapFileNamesToIFile;
  212. private final IProgressMonitor monitor;
  213. String erroredFileName = "";
  214. List lineToOffsetsMapping;
  215. public CompilationReporter( final Map< String, IFile > mapFileNamesToIFile,
  216. final IProgressMonitor monitor )
  217. {
  218. this.mapFileNamesToIFile = mapFileNamesToIFile;
  219. this.monitor = monitor;
  220. }
  221. public void beginReporting()
  222. {
  223. monitor.worked( 1 );
  224. }
  225. public void endReporting()
  226. {
  227. // TODO: emp - tentative location for this method call. The thing
  228. // is, generated*() notifications are sent
  229. // before this method is called. In addition, it seems only the
  230. // output folders should be refreshed.
  231. // The line:
  232. // javaProject.getProject().refreshLocal(IResource.DEPTH_INFINITE,
  233. // new NullProgressMonitor());
  234. // seems to imply otherwise. Am I missing something?
  235. refreshOutput();
  236. }
  237. public void compilationError( final String fileName,
  238. final int line,
  239. final int startCol,
  240. final int endCol,
  241. final String message,
  242. final String stackTrace )
  243. {
  244. final IFile file = mapFileNamesToIFile.get( fileName );
  245. try
  246. {
  247. // Cache the line to offsets mapping.
  248. if( !fileName.equals( erroredFileName ) )
  249. {
  250. erroredFileName = fileName;
  251. lineToOffsetsMapping = createLineToOffsetMapping( file );
  252. }
  253. }
  254. catch( final IOException e )
  255. {
  256. logException( "Internal error reporting error, please report" + file.getName(), e );
  257. return;
  258. }
  259. catch( final CoreException e )
  260. {
  261. logException( "Internal error reporting error, please report" + file.getName(), e );
  262. }
  263. try
  264. {
  265. getWorkspace().run( new AddErrorMarkerTask( file,
  266. lineToOffsetsMapping,
  267. line,
  268. startCol,
  269. endCol,
  270. message,
  271. stackTrace),
  272. null );
  273. }
  274. catch( final CoreException ce )
  275. {
  276. logException("error compiling " + file.getName(), ce);
  277. }
  278. }
  279. public void generatedAST( final String fileName,
  280. final ModuleNode moduleNode )
  281. {
  282. // TODO: emp - every single artifact must be reported - this only
  283. // stores the module node for the file and
  284. // not all the other dependencies.
  285. // Some refactoring is needed.
  286. model.updateClassNameModuleNodeMap( newList( moduleNode ) );
  287. }
  288. public void generatedClasses( final String fileName,
  289. final String[] classNames,
  290. final String[] classFilePaths )
  291. {
  292. final IProject project = getJavaProject().getProject();
  293. final IPath projectPath = project.getLocation();
  294. try
  295. {
  296. final IFolder outputFolder = project.getFolder( getProjectOutputPath( javaProject ) );
  297. outputFolder.refreshLocal( DEPTH_INFINITE, new NullProgressMonitor() );
  298. for( final String path : classFilePaths )
  299. {
  300. final IPath classFilePath = new Path( path );
  301. final int count = classFilePath.matchingFirstSegments( projectPath );
  302. final IFile classFile = project.getFile( classFilePath.removeFirstSegments( count ) );
  303. try
  304. {
  305. classFile.setDerived( true );
  306. }
  307. catch( final CoreException e )
  308. {
  309. logException( e.getMessage(), e );
  310. }
  311. }
  312. }
  313. catch( final CoreException e )
  314. {
  315. logException( e.getMessage(), e );
  316. }
  317. final IFile file = mapFileNamesToIFile.get( fileName );
  318. fireGroovyFileBuilt( file );
  319. }
  320. public void beginReporting( final String fileName ) {}
  321. public void endReporting( final String fileName ) {}
  322. public void generatedCST( final String fileName, final GroovySourceAST cst ) {}
  323. }
  324. /**
  325. * Compilation reporter when rebuilding a specific AST from an input stream.
  326. */
  327. class InputStreamCompileReporter
  328. implements IGroovyCompilationReporter
  329. {
  330. private final IFile file;
  331. private final String sourceCode;
  332. private List lineToOffsetsMapping;
  333. InputStreamCompileReporter( final IFile file,
  334. final String sourceCode )
  335. {
  336. this.file = file;
  337. this.sourceCode = sourceCode;
  338. }
  339. public void beginReporting( final String fileName )
  340. {
  341. deleteErrorMarkers( file );
  342. }
  343. public void compilationError( final String fileName,
  344. final int line,
  345. final int startCol,
  346. final int endCol,
  347. final String message,
  348. final String stackTrace )
  349. {
  350. if( lineToOffsetsMapping == null )
  351. lineToOffsetsMapping = createLineToOffsetMapping( sourceCode );
  352. try
  353. {
  354. getWorkspace().run( new AddErrorMarkerTask( file, lineToOffsetsMapping, line, startCol, endCol, message, stackTrace ), null );
  355. }
  356. catch( final CoreException ce )
  357. {
  358. logException( "error compiling " + file.getName(), ce );
  359. }
  360. }
  361. public void generatedAST( final String fileName,
  362. final ModuleNode moduleNode )
  363. {
  364. // TODO: emp - fix this with a new API once I figure out what is
  365. // happening in
  366. // GroovyProjectModel.
  367. model.updateClassNameModuleNodeMap( newList( moduleNode ) );
  368. // TODO: emp - Hack! GroovyBuildListener needs a fuller API to
  369. // report building of ASTs
  370. // and class files.
  371. // Many listeners care only about the AST.
  372. fireGroovyFileBuilt( file );
  373. }
  374. public void beginReporting() {}
  375. public void endReporting() {}
  376. public void endReporting( final String fileName) {}
  377. public void generatedCST( final String fileName, final GroovySourceAST cst ) {}
  378. public void generatedClasses( final String fileName, final String[] classNames, final String[] classFilePaths) {}
  379. }
  380. private final IJavaProject javaProject;
  381. private final IPersistentPreferenceStore preferenceStore;
  382. private final GroovyProjectModel model = new GroovyProjectModel(this);
  383. public static final String GROOVY_ERROR_MARKER = "org.codehaus.groovy.eclipse.groovyFailure";
  384. private final ILock listenersLock = Job.getJobManager().newLock();
  385. private final List< GroovyBuildListener > listeners = newList();
  386. private final IGroovyCompiler compiler = new GroovyCompiler();
  387. // TODO: We need to keep track if a full build has occurred without errors
  388. // or not.
  389. // If it hasn't, methods that do things like return all runnable classes
  390. // with a main
  391. // won't work correctly unless a full build occurrs to create Module Nodes
  392. // for all the
  393. // source files in the project.
  394. // private boolean hasFullBuildHappened = false;
  395. /**
  396. * Construct new groovy project.
  397. *
  398. * @param javaProject
  399. */
  400. public GroovyProject( final IJavaProject javaProject )
  401. {
  402. this.javaProject = javaProject;
  403. preferenceStore = preferenceStore( javaProject.getProject() );
  404. preferenceStore.addPropertyChangeListener( new PropertyChangeListener( javaProject.getProject() ) );
  405. GroovyCore.getPreferenceStore()
  406. .addPropertyChangeListener( new PropertyChangeListener( javaProject.getProject() ) );
  407. trace( "constructing Groovy Project " + javaProject.getElementName() );
  408. // Note that enabling debug will disable class generation
  409. // compilerConfiguration.setDebug(true);
  410. }
  411. public GroovyProjectModel getModel()
  412. {
  413. return model;
  414. }
  415. public GroovyProjectModel model()
  416. {
  417. return getModel();
  418. }
  419. public IContainer getGroovyOutputFolder()
  420. {
  421. final String outputPath = defaultString( getProjectOutputPath( getJavaProject() ) ).trim();
  422. if( isBlank( outputPath ) || StringUtils.equals( "/", outputPath ) || StringUtils.equals( "\\", outputPath ) )
  423. return getJavaProject().getProject();
  424. return getJavaProject().getProject().getFolder( outputPath );
  425. }
  426. public IPersistentPreferenceStore getPreferenceStore()
  427. {
  428. return preferenceStore;
  429. }
  430. public GroovyProject rebuild( final IProgressMonitor monitor )
  431. {
  432. buildGroovyContent( monitor, FULL_BUILD, filesForFullBuild() );
  433. return this;
  434. }
  435. public boolean isGeneratingClassFiles()
  436. {
  437. return !preferenceStore.getBoolean( GROOVY_DONT_GENERATE_CLASS_FILES );
  438. }
  439. /**
  440. * Overloaded method used by builders to compile groovy files that defaults
  441. * to the variable for generating .class files to what's set in the Groovy
  442. * prefs page
  443. *
  444. * @param monitor
  445. * @param kind
  446. */
  447. public void buildGroovyContent( final IProgressMonitor monitor,
  448. final int kind,
  449. final ChangeSet changeSet)
  450. {
  451. if( monitor.isCanceled() )
  452. return;
  453. buildGroovyContent( monitor, kind, changeSet, isGeneratingClassFiles() );
  454. }
  455. /**
  456. * Used by builders to compile Groovy source
  457. *
  458. * @param monitor
  459. * @param kind
  460. * @param changeSet
  461. * @param generateClassFiles
  462. */
  463. public void buildGroovyContent( final IProgressMonitor progressMonitor,
  464. final int kind,
  465. final ChangeSet changeSet,
  466. final boolean generateClassFiles )
  467. {
  468. final IProgressMonitor monitor = progressMonitor != null ? progressMonitor : new NullProgressMonitor();
  469. try
  470. {
  471. if( monitor.isCanceled() )
  472. return;
  473. final String outputPath = getOutputPath( javaProject );
  474. if( kind == FULL_BUILD )
  475. {
  476. trace("beginning FULL BUILD - GroovyProject.buildGroovyContent()");
  477. // Removing all .class files that we are aware of.
  478. // Making a defensive copy since removeClassFiles() will modify
  479. // the overlying map.
  480. final Set< String > keySet = linkedSet( model.scriptPaths() );
  481. for( final Iterator< String > iterator = keySet.iterator(); iterator.hasNext(); )
  482. {
  483. if( monitor.isCanceled() )
  484. return;
  485. removeClassFiles( iterator.next(), true );
  486. }
  487. model.clear();
  488. }
  489. else
  490. {
  491. trace( "beginning INCREMENTAL BUILD - GroovyProject.buildGroovyContent()" );
  492. // Removing .class files associated with changeSet.filesToRemove
  493. removeClassFiles( changeSet.filesToRemove(), true );
  494. }
  495. for( final Iterator< IFile > it = changeSet.getFilesToBuild().iterator(); it.hasNext(); )
  496. {
  497. if( monitor.isCanceled() )
  498. return;
  499. deleteErrorMarkers( it.next() );
  500. }
  501. trace( "filesToBuild:" + changeSet );
  502. compile( changeSet.filesToBuild(), monitor, outputPath, generateClassFiles );
  503. }
  504. catch( final Exception e )
  505. {
  506. monitor.worked( 1 );
  507. logException( "error building groovy files", e );
  508. }
  509. }
  510. private void compile( final IFile[] files,
  511. final IProgressMonitor monitor,
  512. final String outputPath,
  513. final boolean generateClassFiles )
  514. {
  515. if( monitor.isCanceled() )
  516. return;
  517. final Map< String, IFile > mapFileNamesToIFile = newEmptyLinkedMap();
  518. for( final IFile file : files )
  519. mapFileNamesToIFile.put( file.getLocation().toOSString(), file );
  520. final GroovyCompilerConfigurationBuilder builder = generateClassFiles
  521. ? new GroovyCompilerConfigurationBuilder().buildAST().buildClasses()
  522. : new GroovyCompilerConfigurationBuilder().buildAST();
  523. if( monitor.isCanceled() )
  524. return;
  525. final IGroovyCompilerConfiguration config;
  526. try
  527. {
  528. config = builder.classLoader( getProjectClassLoader() ).outputPath( outputPath ).done();
  529. if( monitor.isCanceled() )
  530. return;
  531. compiler.compile( mapFileNamesToIFile.keySet().toArray( new String[ 0 ] ),
  532. config,
  533. new CompilationReporter( mapFileNamesToIFile, monitor ) );
  534. }
  535. catch( final CoreException e )
  536. {
  537. logException( "Internal Error - please report: project = " + getJavaProject().getProject().getName() + ". " + e.getMessage(), e );
  538. }
  539. catch( final NoClassDefFoundError e )
  540. {
  541. logException( "Internal Error - please report: project = " + getJavaProject().getProject().getName(), e );
  542. }
  543. }
  544. /**
  545. * This method assumes the collection of files are pointing to the groovy
  546. * source code files that have been removed. It extracts the package name by
  547. * querying the scriptPathModuleNodeMap attribute and then looks in the java
  548. * project default output location for files that have the class names given
  549. * by the scriptPathModuleNodeMap attribute.
  550. *
  551. * @param files
  552. */
  553. private void removeClassFiles( final IFile[] files,
  554. final boolean refreshOutput )
  555. {
  556. if( files == null || files.length == 0 )
  557. return;
  558. for( final IFile file : files )
  559. removeClassFiles( getSourceFileKey( file ), refreshOutput );
  560. }
  561. private void removeClassFiles( final String filePath,
  562. final boolean refreshOutput )
  563. {
  564. if( isBlank( filePath ) )
  565. return;
  566. removeClassFiles( model.removeModuleNodes( filePath ), refreshOutput );
  567. }
  568. private void removeClassFiles( final List< ModuleNode > modules,
  569. final boolean refreshOutput )
  570. {
  571. if( modules == null || modules.size() == 0 )
  572. return;
  573. final List< ClassNode > classes = getClassesForModules( modules );
  574. for( final Iterator< ClassNode > iterator = classes.iterator(); iterator.hasNext(); )
  575. removeClassFiles( iterator.next(), refreshOutput );
  576. }
  577. @SuppressWarnings("unchecked")
  578. public void removeClassFiles( final ModuleNode module,
  579. final boolean refreshOutput )
  580. {
  581. if( module == null )
  582. return;
  583. final List< ClassNode > classes = module.getClasses();
  584. for( final Iterator< ClassNode > iterator = classes.iterator(); iterator.hasNext(); )
  585. removeClassFiles( iterator.next(), refreshOutput );
  586. }
  587. public void removeClassFiles( final ClassNode clase,
  588. final boolean refreshOutput )
  589. {
  590. if( clase == null )
  591. return;
  592. try
  593. {
  594. final String output = getOutputOSPath( javaProject );
  595. final String packageLocation = clase.hasPackageName()
  596. ? output + File.separator + clase.getPackageName().replace( '.', File.separatorChar )
  597. : output;
  598. final File directory = new File( packageLocation );
  599. if( !directory.exists() || !directory.isDirectory() )
  600. return;
  601. final File[] directoryFiles = directory.listFiles();
  602. removeClassFiles( clase.getNameWithoutPackage(), directoryFiles, refreshOutput );
  603. }
  604. catch( final JavaModelException e )
  605. {
  606. logException( "Error getting java output location for " + javaProject.getElementName(), e );
  607. }
  608. }
  609. private void removeClassFiles( final String className,
  610. final File[] directoryFiles,
  611. final boolean refreshOutput )
  612. {
  613. if( className == null || className.trim().equals( "" ) || directoryFiles == null )
  614. return;
  615. for( final File directoryFile : directoryFiles )
  616. {
  617. try
  618. {
  619. if( directoryFile.getName().equals( className + ".class" ) )
  620. {
  621. forceDelete( directoryFile );
  622. continue;
  623. }
  624. if( directoryFile.getName().startsWith( className + "$" ) && directoryFile.getName().endsWith( ".class" ) )
  625. {
  626. forceDelete( directoryFile );
  627. continue;
  628. }
  629. }
  630. catch( final IOException ioe )
  631. {
  632. logException( "Error deleting " + directoryFile.getName(), ioe );
  633. }
  634. }
  635. if( refreshOutput )
  636. refreshOutput();
  637. }
  638. /**
  639. * Recompile a source file without creating a .class file. This method is
  640. * useful for recreating the ModuleNode from the groovy compiler if the
  641. * ModuleNode has yet to be created during the current working session.
  642. *
  643. * @param file
  644. */
  645. public void compileGroovyFile( final IFile file )
  646. {
  647. compileGroovyFile( file, false );
  648. }
  649. /**
  650. * Compiles a Groovy file that is represented by the given input stream.
  651. * This method is useful for tooling that contains an copy of the file that
  652. * is in the process of being modified and wants to update the AST to
  653. * reflect the updated content. Class files will <em>not</em> be created.
  654. * The input stream is closed by this method.
  655. *
  656. * @param file
  657. * @param inputStream
  658. */
  659. public void compileGroovyFile( final IFile file,
  660. final InputStream input )
  661. {
  662. try
  663. {
  664. final String sourceCode = readSourceCode( input );
  665. final InputStream inputStream = new ByteArrayInputStream( sourceCode.getBytes( file.getCharset() ) );
  666. final String fullFileName = file.getLocation().toOSString();
  667. final IGroovyCompilerConfiguration config
  668. = new GroovyCompilerConfigurationBuilder().buildAST()
  669. .errorRecovery()
  670. .resolveAST()
  671. .classLoader( getProjectClassLoader() )
  672. .outputPath( getOutputOSPath( javaProject ) )
  673. .done();
  674. compiler.compile( fullFileName, inputStream, config, new InputStreamCompileReporter( file, sourceCode ) );
  675. }
  676. catch( final JavaModelException e )
  677. {
  678. logException( e.getMessage(), e );
  679. }
  680. catch( final CoreException e )
  681. {
  682. logException( e.getMessage(), e );
  683. }
  684. catch( final IOException e )
  685. {
  686. logException( e.getMessage(), e );
  687. }
  688. }
  689. private String readSourceCode( final InputStream inputStream )
  690. throws IOException
  691. {
  692. final BufferedReader reader = new BufferedReader( new InputStreamReader( inputStream ) );
  693. try
  694. {
  695. final StringBuffer buffer = new StringBuffer();
  696. final char[] cbuffer = new char[ 1024 ];
  697. int count;
  698. while( ( count = reader.read( cbuffer ) ) != -1 )
  699. buffer.append( cbuffer, 0, count );
  700. return buffer.toString();
  701. }
  702. finally
  703. {
  704. closeQuietly( reader );
  705. closeQuietly( inputStream );
  706. }
  707. }
  708. /**
  709. * Recompile a source file and specify the creation of a .class file.
  710. */
  711. public void compileGroovyFile( final IFile file,
  712. final boolean generateClassFiles )
  713. {
  714. final ChangeSet changeSet = new ChangeSet().addFileToBuild(file);
  715. buildGroovyContent( new NullProgressMonitor(), INCREMENTAL_BUILD, changeSet, generateClassFiles );
  716. }
  717. public static IPersistentPreferenceStore preferenceStore( final IProject project )
  718. {
  719. return new ScopedPreferenceStore( new ProjectScope( project ),
  720. "org.codehaus.groovy.eclipse.preferences" );
  721. }
  722. /**
  723. * @param excludeGroovyRuntime If true, the class path will not contain the
  724. * Groovy runtime.
  725. * @return The class path of this project.
  726. * @throws CoreException
  727. */
  728. public Set< String > getClassPath()
  729. throws CoreException
  730. {
  731. return getClasspath( getJavaProject(), newList( new IJavaProject[ 0 ] ) );
  732. }
  733. /**
  734. * Get class path string specified using the OS specific path separator.
  735. *
  736. * @return The class path or an empty string if no class path is defined.
  737. * @param excludeGroovyRuntime Flag to exclude the runtime. The runtime
  738. * should be excuded for IDE code, e.g. completion and building, but
  739. * included for launchers etc.
  740. * @throws CoreException
  741. */
  742. public String getOSClassPath()
  743. throws CoreException
  744. {
  745. final Set< String > setOfClassPath = getClasspath( javaProject, newList( new IJavaProject[ 0 ] ) );
  746. if( setOfClassPath.size() == 0 )
  747. return "";
  748. final StringBuffer classPath = new StringBuffer();
  749. final Iterator< String > iter = setOfClassPath.iterator();
  750. while( iter.hasNext() )
  751. classPath.append( iter.next().toString() ).append( File.pathSeparator );
  752. return classPath.substring( 0, classPath.length() - 1 );
  753. }
  754. /**
  755. * @return The class loader used by the project for compiling, or null if
  756. * one is not available.
  757. * @throws CoreException
  758. */
  759. public ClassLoader getProjectClassLoader()
  760. throws CoreException
  761. {
  762. // if( projectClassLoader == null )
  763. // projectClassLoader = newProjectClassLoader();
  764. return newProjectClassLoader();
  765. // return projectClassLoader;
  766. }
  767. /**
  768. * @return A new class loader for the project.
  769. * @throws CoreException
  770. */
  771. public ClassLoader newProjectClassLoader()
  772. throws CoreException
  773. {
  774. // set the parent classloader to the org.codehaus.groovy BundleLoader
  775. // so we know ANTLR always comes from the same known classloader and thus
  776. // avoid ClassCastExceptions that come from class being loaded from different
  777. // classloaders
  778. return new URLClassLoader( getClassPathAsUrls(), org.codehaus.groovy.plugin.Activator.class.getClassLoader() );
  779. }
  780. /**
  781. * @return The class path as URLs, useful for creating class loaders.
  782. * @param excludeGroovyRuntime Flag to exclude the runtime. The runtime
  783. * should be excuded for IDE code, e.g. completion and building, but
  784. * included for launchers etc.
  785. * @throws CoreException
  786. */
  787. public URL[] getClassPathAsUrls()
  788. throws CoreException
  789. {
  790. final List< String > classPath = newList( getOSClassPath().split( File.pathSeparator ) );
  791. final List< URL > classpathUrls = newEmptyList();
  792. for( final Iterator< String > iter = classPath.iterator(); iter.hasNext(); )
  793. {
  794. try
  795. {
  796. classpathUrls.add( new File( iter.next() ).toURI().toURL() );
  797. }
  798. catch( final MalformedURLException e )
  799. {
  800. logException( "Error converting File to URL", e );
  801. throw new RuntimeException( e );
  802. }
  803. }
  804. return classpathUrls.toArray( new URL[ classpathUrls.size() ] );
  805. }
  806. public static Set< String > getClasspath( final IJavaProject project,
  807. final List< IJavaProject > visited )
  808. throws CoreException
  809. {
  810. final Set< String > set = linkedSet();
  811. if( project == null || !project.exists() )
  812. return set;
  813. if( visited.contains( project ) )
  814. return set;
  815. visited.add( project );
  816. collectPackageFragmentRootPaths( set, project, visited );
  817. collectClassPathEntryPaths( set, project, visited );
  818. if( !project.getProject().hasNature( GroovyNature.GROOVY_NATURE ) )
  819. return set;
  820. final String outputPath = getOutputPath( project );
  821. if( !outputPath.trim().equals( "" ) )
  822. set.add( outputPath );
  823. return set;
  824. }
  825. private static void collectPackageFragmentRootPaths( final Set< String > results,
  826. final IJavaProject project,
  827. final List visited )
  828. throws JavaModelException
  829. {
  830. final IPackageFragmentRoot[] fragRoots = project.getPackageFragmentRoots();
  831. for( final IPackageFragmentRoot fragRoot : fragRoots )
  832. {
  833. final IResource resource = fragRoot.getCorrespondingResource();
  834. // Fix for: GROOVY-1825 - emp
  835. // The first project visited is the source or the compile request.
  836. // Its source folder is placed on the
  837. // class path.
  838. // External project source folders must not be placed on the class
  839. // path as the source code will be compiled,
  840. // however their classes will appear in this projects output folder
  841. // which is incorrect behaviour.
  842. // So check visit count == 1 to prevent other source paths from
  843. // being added to the class path.
  844. if( resource != null && visited.size() == 1 )
  845. results.add( resource.getLocation().toString() );
  846. else
  847. results.add( fragRoot.getPath().toString() );
  848. }
  849. }
  850. private static void collectClassPathEntryPaths( final Set< String > results,
  851. final IJavaProject project,
  852. final List< IJavaProject > visited )
  853. throws CoreException, JavaModelException
  854. {
  855. final IWorkspaceRoot root = getWorkspace().getRoot();
  856. for( final IClasspathEntry entry : project.getResolvedClasspath( false ) )
  857. {
  858. final IResource resource = root.findMember( entry.getPath() );
  859. switch( entry.getEntryKind() )
  860. {
  861. case IClasspathEntry.CPE_LIBRARY:
  862. collectLibraryPaths( results, entry, resource );
  863. break;
  864. case IClasspathEntry.CPE_PROJECT:
  865. collectDependentProjectPaths( results, resource, visited );
  866. break;
  867. case IClasspathEntry.CPE_SOURCE:
  868. collectOutputLocations( results, project, root, entry );
  869. break;
  870. }
  871. }
  872. }
  873. private static void collectOutputLocations( final Set< String > results,
  874. final IJavaProject project,
  875. final IWorkspaceRoot root,
  876. final IClasspathEntry entry )
  877. throws JavaModelException
  878. {
  879. if( entry.getOutputLocation() != null )
  880. {
  881. results.add( root.getFolder( entry.getOutputLocation() ).getRawLocation().toString() );
  882. return;
  883. }
  884. if( project.exists() )
  885. {
  886. final IPath projectOutputLocation = project.getOutputLocation();
  887. final IResource member = root.findMember( projectOutputLocation );
  888. if( member != null && member.exists() )
  889. {
  890. final IPath location = member.getLocation();
  891. results.add( location.toString() );
  892. }
  893. }
  894. }
  895. private static void collectDependentProjectPaths( final Set< String > results,
  896. final IResource resource,
  897. final List< IJavaProject > visited )
  898. throws CoreException
  899. {
  900. final IJavaProject referencedProject = JavaCore.create( ( IProject )resource );
  901. if( referencedProject != null && referencedProject.getProject().exists() )
  902. results.addAll( getClasspath( referencedProject, visited ) );
  903. }
  904. private static void collectLibraryPaths( final Set< String > results,
  905. final IClasspathEntry entry,
  906. final IResource resource )
  907. {
  908. if( resource != null )
  909. results.add( resource.getLocation().toString() );
  910. else
  911. results.add( entry.getPath().toString() );
  912. }
  913. /**
  914. * Processing the exception that is thrown when there are compiler errors
  915. * and creates errors markers for the files that have errors.
  916. *
  917. * @param file - list of IFiles to compile for a full build
  918. * @param e
  919. */
  920. private void handleCompilationError( final List< IFile > fileList,
  921. final Exception e )
  922. {
  923. // GroovyCore.trace("compilation error : " + e.getMessage());
  924. final IFile file = fileList.get( 0 );
  925. try
  926. {
  927. getWorkspace().run( new AddErrorMarker( fileList, e ), null );
  928. }
  929. catch( final CoreException ce )
  930. {
  931. logException( "error compiling " + file.getName(), ce );
  932. }
  933. }
  934. /**
  935. * @param listener
  936. */
  937. public void addBuildListener( final GroovyBuildListener listener )
  938. {
  939. try
  940. {
  941. listenersLock.acquire();
  942. listeners.add( listener );
  943. }
  944. finally
  945. {
  946. listenersLock.release();
  947. }
  948. }
  949. /**
  950. * @param listener
  951. */
  952. public void removeBuildListener( final GroovyBuildListener listener )
  953. {
  954. try
  955. {
  956. listenersLock.acquire();
  957. listeners.remove( listener );
  958. }
  959. finally
  960. {
  961. listenersLock.release();
  962. }
  963. }
  964. class FireFileBuiltAction
  965. implements Runnable
  966. {
  967. private final IFile file;
  968. public void run()
  969. {
  970. try
  971. {
  972. listenersLock.acquire();
  973. for( final Iterator< GroovyBuildListener > iter = listeners.iterator(); iter.hasNext();)
  974. {
  975. final GroovyBuildListener buildListener = iter.next();
  976. try
  977. {
  978. buildListener.fileBuilt( file );
  979. }
  980. catch( final Exception e )
  981. {
  982. logException( "Exception in GroovyBuildListener", e );
  983. }
  984. }
  985. }
  986. finally
  987. {
  988. listenersLock.release();
  989. }
  990. }
  991. public FireFileBuiltAction( final IFile file )
  992. {
  993. this.file = file;
  994. }
  995. }
  996. private void fireGroovyFileBuilt( final IFile file )
  997. {
  998. getDefault().asyncExec( new FireFileBuiltAction( file ) );
  999. }
  1000. /**
  1001. * @return Returns the javaProject.
  1002. */
  1003. public IJavaProject getJavaProject()
  1004. {
  1005. return javaProject;
  1006. }
  1007. /**
  1008. * This method looks for any scripts ( ModuleNodes ) that declare themselves
  1009. * to be in a package where their location in the source folder hierarchy
  1010. * says otherwise. So if you have a Script that has a class A where the
  1011. * fully qualified type name for A is pack1.A, then the script had better
  1012. * been in a source folder under the subdirectory pack1. This is to resolve
  1013. * JIRA Issue: GROOVY-1361
  1014. */
  1015. private IFile[] checkForInvalidPackageDeclarations()
  1016. {
  1017. if( preferenceStore.getBoolean( GROOVY_DONT_CHECK_PACKAGE_VS_SRC_PATH ) )
  1018. return new IFile[ 0 ];
  1019. final List< IFile > invalidList = newList();
  1020. for( final Iterator< String > keyIterator = model.scriptPaths().iterator(); keyIterator.hasNext(); )
  1021. {
  1022. final String key = keyIterator.next();
  1023. final List< ModuleNode > moduleList = model.getModuleNodes( key );
  1024. if( moduleList.isEmpty() )
  1025. continue;
  1026. for( final Iterator< ModuleNode > moduleIterator = moduleList.iterator(); moduleIterator.hasNext(); )
  1027. {
  1028. final ModuleNode module = moduleIterator.next();
  1029. final String packageNameString = module.getPackageName() != null ? module.getPackageName() : "";
  1030. final File moduleFile = new File( module.getDescription() );
  1031. final String packageLocation = moduleFile.getParent();
  1032. if( packageLocation == null )
  1033. continue;
  1034. final IPath[] entries = getSourceDirectories();
  1035. boolean found = false;
  1036. final Path packagePathObj = new Path( packageLocation );
  1037. // We are checking here that the script path actually lies in a
  1038. // source directory for this project.
  1039. // If it does not, the error message does not make alot of
  1040. // sense.
  1041. for( final IPath entry : entries )
  1042. {
  1043. if( !entry.isPrefixOf( packagePathObj ) )
  1044. continue;
  1045. found = true;
  1046. break;
  1047. }
  1048. if( !found )
  1049. continue;
  1050. final String packageName = packageNameString.endsWith( "." )
  1051. ? packageNameString.substring( 0, packageNameString.length() - 1 )
  1052. : packageNameString;
  1053. final String packagePath = packageName.replace( '.', File.separatorChar );
  1054. if( isBlank( packagePath ) )
  1055. {
  1056. // This is for the default package... why doesn't java just
  1057. // make it illegal??
  1058. found = false;
  1059. for( final IPath entry : entries )
  1060. {
  1061. if( !entry.toOSString().equals( packageLocation ) )
  1062. continue;
  1063. found = true;
  1064. break;
  1065. }
  1066. if( found )
  1067. continue;
  1068. }
  1069. if( !packagePath.equals( "" ) && packageLocation.endsWith( packagePath ) )
  1070. continue;
  1071. final IProject project = javaProject.getProject();
  1072. final String scriptPathString = removeStart( module.getDescription(), project.getLocation().toOSString() );
  1073. final IPath scriptPath = new Path( scriptPathString );
  1074. final IFile scriptFile = project.getFile( scriptPath );
  1075. if( !scriptFile.exists() )
  1076. continue;
  1077. final List< IFile > fileList = newList();
  1078. fileList.add( scriptFile );
  1079. final String prefix = "Invalid Package declaration in script: ";
  1080. final String message = prefix + module.getDescription() + " is not in a source folder matching the package declaration: " + packageName;
  1081. removeDuplicateMarker( module, scriptFile, prefix );
  1082. final SyntaxException se = new SyntaxException(message,1,1);
  1083. handleCompilationError( fileList, se);
  1084. invalidList.add( scriptFile );
  1085. }
  1086. }
  1087. return invalidList.toArray( new IFile[ 0 ] );
  1088. }
  1089. public IPath[] getSourceDirectories()
  1090. {
  1091. final List< IPath > list = newList();
  1092. try
  1093. {
  1094. for( final IClasspathEntry entry : javaProject.getResolvedClasspath( false ) )
  1095. {
  1096. if( entry.getEntryKind() != IClasspathEntry.CPE_SOURCE )
  1097. continue;
  1098. final IResource resource = javaProject.getProject().findMember( entry.getPath().removeFirstSegments( 1 ) );
  1099. if( resource == null || !resource.exists() )
  1100. continue;
  1101. list.add( resource.getLocation() );
  1102. }
  1103. }
  1104. catch( final JavaModelException e )
  1105. {
  1106. logException( "Error getting the classpath: " + e, e );
  1107. return new IPath[ 0 ];
  1108. }
  1109. return list.toArray( new IPath[ 0 ] );
  1110. }
  1111. private void removeDuplicateMarker( final ModuleNode module,
  1112. final IFile scriptFile,
  1113. final String prefix )
  1114. {
  1115. try
  1116. {
  1117. final IMarker[] markers = scriptFile.findMarkers( GROOVY_ERROR_MARKER, false, DEPTH_INFINITE );
  1118. if( markers == null || markers.length == 0 )
  1119. return;
  1120. for( final IMarker marker : markers )
  1121. {
  1122. if( !marker.getAttribute( "message", "" ).startsWith( prefix ) )
  1123. continue;
  1124. marker.delete();
  1125. }
  1126. }
  1127. catch( final CoreException e )
  1128. {
  1129. logException( "Error getting markers: " + GROOVY_ERROR_MARKER + " for script: " + module.getDescription() + ". " + e, e );
  1130. }
  1131. return;
  1132. }
  1133. public GroovyProject refreshOutput()
  1134. {
  1135. final IFile[] invalidScripts = checkForInvalidPackageDeclarations();
  1136. removeClassFiles( invalidScripts, false );
  1137. try
  1138. {
  1139. javaProject.getProject().refreshLocal( DEPTH_INFINITE, new NullProgressMonitor() );
  1140. }
  1141. catch( final CoreException e )
  1142. {
  1143. logException( "Error refreshing output location, the navigator view could be out of sync: " + e, e );
  1144. }
  1145. return this;
  1146. }
  1147. /**
  1148. * Remove the GROOOVY error markers from a groovy file (IFile) in the
  1149. * project
  1150. *
  1151. * @param file
  1152. */
  1153. public static void deleteErrorMarkers( final IFile file )
  1154. {
  1155. try
  1156. {
  1157. // HACK: emp - This check is done before deleting markers, as
  1158. // without it a deadlock between the workspace
  1159. // thread and the reconciler thread occurs when renaming a Groovy
  1160. // file.
  1161. // if(file.exists()) {
  1162. final IMarker[] markers = file.findMarkers( G

Large files files are truncated, but you can click here to view the full file