6ed2fdb33708b58b9f67571e8d369f4322e03f2e
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / core / JavaModelManager.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2003 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials 
4  * are made available under the terms of the Common Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v10.html
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package net.sourceforge.phpdt.internal.core;
12
13 import java.io.BufferedInputStream;
14 import java.io.BufferedOutputStream;
15 import java.io.DataInputStream;
16 import java.io.DataOutputStream;
17 import java.io.File;
18 import java.io.FileInputStream;
19 import java.io.FileOutputStream;
20 import java.io.IOException;
21 import java.text.NumberFormat;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.Iterator;
27 import java.util.Map;
28 import java.util.WeakHashMap;
29 import java.util.zip.ZipFile;
30
31 import net.sourceforge.phpdt.core.ElementChangedEvent;
32 import net.sourceforge.phpdt.core.IClasspathEntry;
33 import net.sourceforge.phpdt.core.ICompilationUnit;
34 import net.sourceforge.phpdt.core.IElementChangedListener;
35 import net.sourceforge.phpdt.core.IJavaElement;
36 import net.sourceforge.phpdt.core.IJavaElementDelta;
37 import net.sourceforge.phpdt.core.IJavaModel;
38 import net.sourceforge.phpdt.core.IJavaProject;
39 import net.sourceforge.phpdt.core.IPackageFragment;
40 import net.sourceforge.phpdt.core.IPackageFragmentRoot;
41 import net.sourceforge.phpdt.core.IParent;
42 import net.sourceforge.phpdt.core.IProblemRequestor;
43 import net.sourceforge.phpdt.core.IWorkingCopy;
44 import net.sourceforge.phpdt.core.JavaCore;
45 import net.sourceforge.phpdt.core.JavaModelException;
46 import net.sourceforge.phpdt.core.WorkingCopyOwner;
47 import net.sourceforge.phpdt.core.compiler.IProblem;
48 import net.sourceforge.phpdt.internal.core.builder.PHPBuilder;
49 import net.sourceforge.phpdt.internal.core.util.Util;
50 import net.sourceforge.phpdt.internal.ui.util.PHPFileUtil;
51 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
52
53 import org.eclipse.core.resources.IFile;
54 import org.eclipse.core.resources.IFolder;
55 import org.eclipse.core.resources.IMarker;
56 import org.eclipse.core.resources.IProject;
57 import org.eclipse.core.resources.IResource;
58 import org.eclipse.core.resources.IResourceDelta;
59 import org.eclipse.core.resources.ISaveContext;
60 import org.eclipse.core.resources.ISaveParticipant;
61 import org.eclipse.core.resources.IWorkspace;
62 import org.eclipse.core.resources.IWorkspaceDescription;
63 import org.eclipse.core.resources.IWorkspaceRoot;
64 import org.eclipse.core.resources.ResourcesPlugin;
65 import org.eclipse.core.runtime.CoreException;
66 import org.eclipse.core.runtime.IPath;
67 import org.eclipse.core.runtime.IProgressMonitor;
68 import org.eclipse.core.runtime.ISafeRunnable;
69 import org.eclipse.core.runtime.IStatus;
70 import org.eclipse.core.runtime.MultiStatus;
71 import org.eclipse.core.runtime.Path;
72 import org.eclipse.core.runtime.Platform;
73 import org.eclipse.core.runtime.Plugin;
74 import org.eclipse.core.runtime.Preferences;
75 import org.eclipse.core.runtime.Status;
76 import org.eclipse.core.runtime.Preferences.PropertyChangeEvent;
77
78 /**
79  * The <code>JavaModelManager</code> manages instances of <code>IJavaModel</code>.
80  * <code>IElementChangedListener</code>s register with the <code>JavaModelManager</code>,
81  * and receive <code>ElementChangedEvent</code>s for all <code>IJavaModel</code>s.
82  * <p>
83  * The single instance of <code>JavaModelManager</code> is available from
84  * the static method <code>JavaModelManager.getJavaModelManager()</code>.
85  */
86 public class JavaModelManager implements ISaveParticipant {     
87         /**
88          * Unique handle onto the JavaModel
89          */
90         final JavaModel javaModel = new JavaModel();
91         
92         /**
93          * Classpath variables pool
94          */
95         public static HashMap Variables = new HashMap(5);
96         public static HashMap PreviousSessionVariables = new HashMap(5);
97         public static HashSet OptionNames = new HashSet(20);
98         public final static String CP_VARIABLE_PREFERENCES_PREFIX = PHPeclipsePlugin.PLUGIN_ID+".classpathVariable."; //$NON-NLS-1$
99 //      public final static String CP_CONTAINER_PREFERENCES_PREFIX = PHPCore.PLUGIN_ID+".classpathContainer."; //$NON-NLS-1$
100         public final static String CP_ENTRY_IGNORE = "##<cp entry ignore>##"; //$NON-NLS-1$
101                 
102         /**
103          * Classpath containers pool
104          */
105         public static HashMap Containers = new HashMap(5);
106         public static HashMap PreviousSessionContainers = new HashMap(5);
107
108         /**
109          * Name of the extension point for contributing classpath variable initializers
110          */
111 //      public static final String CPVARIABLE_INITIALIZER_EXTPOINT_ID = "classpathVariableInitializer" ; //$NON-NLS-1$
112
113         /**
114          * Name of the extension point for contributing classpath container initializers
115          */
116 //      public static final String CPCONTAINER_INITIALIZER_EXTPOINT_ID = "classpathContainerInitializer" ; //$NON-NLS-1$
117
118         /**
119          * Name of the extension point for contributing a source code formatter
120          */
121         public static final String FORMATTER_EXTPOINT_ID = "codeFormatter" ; //$NON-NLS-1$
122         
123         /**
124          * Special value used for recognizing ongoing initialization and breaking initialization cycles
125          */
126         public final static IPath VariableInitializationInProgress = new Path("Variable Initialization In Progress"); //$NON-NLS-1$
127 //      public final static IClasspathContainer ContainerInitializationInProgress = new IClasspathContainer() {
128 //              public IClasspathEntry[] getClasspathEntries() { return null; }
129 //              public String getDescription() { return "Container Initialization In Progress"; } //$NON-NLS-1$
130 //              public int getKind() { return 0; }
131 //              public IPath getPath() { return null; }
132 //              public String toString() { return getDescription(); }
133 //      };
134         
135         private static final String INDEX_MANAGER_DEBUG = PHPeclipsePlugin.PLUGIN_ID + "/debug/indexmanager" ; //$NON-NLS-1$
136         private static final String COMPILER_DEBUG = PHPeclipsePlugin.PLUGIN_ID + "/debug/compiler" ; //$NON-NLS-1$
137         private static final String JAVAMODEL_DEBUG = PHPeclipsePlugin.PLUGIN_ID + "/debug/javamodel" ; //$NON-NLS-1$
138         private static final String CP_RESOLVE_DEBUG = PHPeclipsePlugin.PLUGIN_ID + "/debug/cpresolution" ; //$NON-NLS-1$
139         private static final String ZIP_ACCESS_DEBUG = PHPeclipsePlugin.PLUGIN_ID + "/debug/zipaccess" ; //$NON-NLS-1$
140         private static final String DELTA_DEBUG =PHPeclipsePlugin.PLUGIN_ID + "/debug/javadelta" ; //$NON-NLS-1$
141         private static final String HIERARCHY_DEBUG = PHPeclipsePlugin.PLUGIN_ID + "/debug/hierarchy" ; //$NON-NLS-1$
142         private static final String POST_ACTION_DEBUG = PHPeclipsePlugin.PLUGIN_ID + "/debug/postaction" ; //$NON-NLS-1$
143         private static final String BUILDER_DEBUG = PHPeclipsePlugin.PLUGIN_ID + "/debug/builder" ; //$NON-NLS-1$
144         private static final String COMPLETION_DEBUG = PHPeclipsePlugin.PLUGIN_ID + "/debug/completion" ; //$NON-NLS-1$
145         private static final String SELECTION_DEBUG = PHPeclipsePlugin.PLUGIN_ID + "/debug/selection" ; //$NON-NLS-1$
146         private static final String SHARED_WC_DEBUG = PHPeclipsePlugin.PLUGIN_ID + "/debug/sharedworkingcopy" ; //$NON-NLS-1$
147         private static final String SEARCH_DEBUG = PHPeclipsePlugin.PLUGIN_ID + "/debug/search" ; //$NON-NLS-1$
148
149         public final static IWorkingCopy[] NoWorkingCopy = new IWorkingCopy[0];
150         
151         /**
152          * Table from WorkingCopyOwner to a table of ICompilationUnit (working copy handle) to PerWorkingCopyInfo.
153          * NOTE: this object itself is used as a lock to synchronize creation/removal of per working copy infos
154          */
155         protected Map perWorkingCopyInfos = new HashMap(5);
156         /**
157          * Returns whether the given full path (for a package) conflicts with the output location
158          * of the given project.
159          */
160         public static boolean conflictsWithOutputLocation(IPath folderPath, JavaProject project) {
161                 try {
162                         IPath outputLocation = project.getOutputLocation();
163                         if (outputLocation == null) {
164                                 // in doubt, there is a conflict
165                                 return true;
166                         }
167                         if (outputLocation.isPrefixOf(folderPath)) {
168                                 // only allow nesting in project's output if there is a corresponding source folder
169                                 // or if the project's output is not used (in other words, if all source folders have their custom output)
170                                 IClasspathEntry[] classpath = project.getResolvedClasspath(true);
171                                 boolean isOutputUsed = false;
172                                 for (int i = 0, length = classpath.length; i < length; i++) {
173                                         IClasspathEntry entry = classpath[i];
174                                         if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
175                                                 if (entry.getPath().equals(outputLocation)) {
176                                                         return false;
177                                                 }
178                                                 if (entry.getOutputLocation() == null) {
179                                                         isOutputUsed = true;
180                                                 }
181                                         }
182                                 }
183                                 return isOutputUsed;
184                         }
185                         return false;
186                 } catch (JavaModelException e) {
187                         // in doubt, there is a conflict
188                         return true;
189                 }
190         }
191
192 //      public static IClasspathContainer containerGet(IJavaProject project, IPath containerPath) {     
193 //              Map projectContainers = (Map)Containers.get(project);
194 //              if (projectContainers == null){
195 //                      return null;
196 //              }
197 //              IClasspathContainer container = (IClasspathContainer)projectContainers.get(containerPath);
198 //              return container;
199 //      }
200
201 //      public static void containerPut(IJavaProject project, IPath containerPath, IClasspathContainer container){
202 //
203 //              Map projectContainers = (Map)Containers.get(project);
204 //              if (projectContainers == null){
205 //                      projectContainers = new HashMap(1);
206 //                      Containers.put(project, projectContainers);
207 //              }
208 //
209 //              if (container == null) {
210 //                      projectContainers.remove(containerPath);
211 //                      Map previousContainers = (Map)PreviousSessionContainers.get(project);
212 //                      if (previousContainers != null){
213 //                              previousContainers.remove(containerPath);
214 //                      }
215 //              } else {
216 //                      projectContainers.put(containerPath, container);
217 //              }
218 //
219 //              // do not write out intermediate initialization value
220 //              if (container == JavaModelManager.ContainerInitializationInProgress) {
221 //                      return;
222 //              }
223 //              Preferences preferences = PHPeclipsePlugin.getPlugin().getPluginPreferences();
224 //              String containerKey = CP_CONTAINER_PREFERENCES_PREFIX+project.getElementName() +"|"+containerPath;//$NON-NLS-1$
225 //              String containerString = CP_ENTRY_IGNORE;
226 //              try {
227 //                      if (container != null) {
228 //                              containerString = ((JavaProject)project).encodeClasspath(container.getClasspathEntries(), null, false);
229 //                      }
230 //              } catch(JavaModelException e){
231 //              }
232 //              preferences.setDefault(containerKey, CP_ENTRY_IGNORE); // use this default to get rid of removed ones
233 //              preferences.setValue(containerKey, containerString);
234 //              PHPeclipsePlugin.getPlugin().savePluginPreferences();
235 //      }
236
237         /**
238          * Returns the Java element corresponding to the given resource, or
239          * <code>null</code> if unable to associate the given resource
240          * with a Java element.
241          * <p>
242          * The resource must be one of:<ul>
243          *      <li>a project - the element returned is the corresponding <code>IJavaProject</code></li>
244          *      <li>a <code>.java</code> file - the element returned is the corresponding <code>ICompilationUnit</code></li>
245          *      <li>a <code>.class</code> file - the element returned is the corresponding <code>IClassFile</code></li>
246          *      <li>a <code>.jar</code> file - the element returned is the corresponding <code>IPackageFragmentRoot</code></li>
247          *  <li>a folder - the element returned is the corresponding <code>IPackageFragmentRoot</code>
248          *                      or <code>IPackageFragment</code></li>
249          *  <li>the workspace root resource - the element returned is the <code>IJavaModel</code></li>
250          *      </ul>
251          * <p>
252          * Creating a Java element has the side effect of creating and opening all of the
253          * element's parents if they are not yet open.
254          */
255         public static IJavaElement create(IResource resource, IJavaProject project) {
256                 if (resource == null) {
257                         return null;
258                 }
259                 int type = resource.getType();
260                 switch (type) {
261                         case IResource.PROJECT :
262                                 return JavaCore.create((IProject) resource);
263                         case IResource.FILE :
264                                 return create((IFile) resource, project);
265                         case IResource.FOLDER :
266                                 return create((IFolder) resource, project);
267                         case IResource.ROOT :
268                                 return JavaCore.create((IWorkspaceRoot) resource);
269                         default :
270                                 return null;
271                 }
272         }
273
274         /**
275          * Returns the Java element corresponding to the given file, its project being the given
276          * project.
277          * Returns <code>null</code> if unable to associate the given file
278          * with a Java element.
279          *
280          * <p>The file must be one of:<ul>
281          *      <li>a <code>.java</code> file - the element returned is the corresponding <code>ICompilationUnit</code></li>
282          *      <li>a <code>.class</code> file - the element returned is the corresponding <code>IClassFile</code></li>
283          *      <li>a <code>.jar</code> file - the element returned is the corresponding <code>IPackageFragmentRoot</code></li>
284          *      </ul>
285          * <p>
286          * Creating a Java element has the side effect of creating and opening all of the
287          * element's parents if they are not yet open.
288          */
289         public static IJavaElement create(IFile file, IJavaProject project) {
290                 if (file == null) {
291                         return null;
292                 }
293                 if (project == null) {
294                         project = JavaCore.create(file.getProject());
295                 }
296         
297                 if (file.getFileExtension() != null) {
298                         String name = file.getName();
299                         if (PHPFileUtil.isValidPHPUnitName(name))
300                         //if (PHPFileUtil.isPHPFile(file))
301                                 return createCompilationUnitFrom(file, project);
302 //                      if (ProjectPrefUtil.isValidClassFileName(name))
303 //                              return createClassFileFrom(file, project);
304 //                      if (ProjectPrefUtil.isArchiveFileName(name))
305 //                              return createJarPackageFragmentRootFrom(file, project);
306                 }
307                 return null;
308         }
309
310         /**
311          * Returns the package fragment or package fragment root corresponding to the given folder,
312          * its parent or great parent being the given project. 
313          * or <code>null</code> if unable to associate the given folder with a Java element.
314          * <p>
315          * Note that a package fragment root is returned rather than a default package.
316          * <p>
317          * Creating a Java element has the side effect of creating and opening all of the
318          * element's parents if they are not yet open.
319          */
320         public static IJavaElement create(IFolder folder, IJavaProject project) {
321                 if (folder == null) {
322                         return null;
323                 }
324                 if (project == null) {
325                         project = JavaCore.create(folder.getProject());
326                 }
327                 IJavaElement element = determineIfOnClasspath(folder, project);
328                 if (conflictsWithOutputLocation(folder.getFullPath(), (JavaProject)project)
329                         || (folder.getName().indexOf('.') >= 0 
330                                 && !(element instanceof IPackageFragmentRoot))) {
331                         return null; // only package fragment roots are allowed with dot names
332                 } else {
333                         return element;
334                 }
335         }
336
337         /**
338          * Creates and returns a class file element for the given <code>.class</code> file,
339          * its project being the given project. Returns <code>null</code> if unable
340          * to recognize the class file.
341          */
342 //      public static IClassFile createClassFileFrom(IFile file, IJavaProject project ) {
343 //              if (file == null) {
344 //                      return null;
345 //              }
346 //              if (project == null) {
347 //                      project = PHPCore.create(file.getProject());
348 //              }
349 //              IPackageFragment pkg = (IPackageFragment) determineIfOnClasspath(file, project);
350 //              if (pkg == null) {
351 //                      // fix for 1FVS7WE
352 //                      // not on classpath - make the root its folder, and a default package
353 //                      IPackageFragmentRoot root = project.getPackageFragmentRoot(file.getParent());
354 //                      pkg = root.getPackageFragment(IPackageFragment.DEFAULT_PACKAGE_NAME);
355 //              }
356 //              return pkg.getClassFile(file.getName());
357 //      }
358         
359         /**
360          * Creates and returns a compilation unit element for the given <code>.java</code> 
361          * file, its project being the given project. Returns <code>null</code> if unable
362          * to recognize the compilation unit.
363          */
364         public static ICompilationUnit createCompilationUnitFrom(IFile file, IJavaProject project) {
365
366                 if (file == null) return null;
367
368                 if (project == null) {
369                         project = JavaCore.create(file.getProject());
370                 }
371                 IPackageFragment pkg = (IPackageFragment) determineIfOnClasspath(file, project);
372                 if (pkg == null) {
373                         // not on classpath - make the root its folder, and a default package
374                         IPackageFragmentRoot root = project.getPackageFragmentRoot(file.getParent());
375                         pkg = root.getPackageFragment(IPackageFragment.DEFAULT_PACKAGE_NAME);
376                         
377                         if (VERBOSE){
378                                 System.out.println("WARNING : creating unit element outside classpath ("+ Thread.currentThread()+"): " + file.getFullPath()); //$NON-NLS-1$//$NON-NLS-2$
379                         }
380                 }
381                 return pkg.getCompilationUnit(file.getName());
382         }
383         /**
384          * Creates and returns a handle for the given JAR file, its project being the given project.
385          * The Java model associated with the JAR's project may be
386          * created as a side effect. 
387          * Returns <code>null</code> if unable to create a JAR package fragment root.
388          * (for example, if the JAR file represents a non-Java resource)
389          */
390 //      public static IPackageFragmentRoot createJarPackageFragmentRootFrom(IFile file, IJavaProject project) {
391 //              if (file == null) {
392 //                      return null;
393 //              }
394 //              if (project == null) {
395 //                      project = PHPCore.create(file.getProject());
396 //              }
397 //      
398 //              // Create a jar package fragment root only if on the classpath
399 //              IPath resourcePath = file.getFullPath();
400 //              try {
401 //                      IClasspathEntry[] entries = ((JavaProject)project).getResolvedClasspath(true);
402 //                      for (int i = 0, length = entries.length; i < length; i++) {
403 //                              IClasspathEntry entry = entries[i];
404 //                              IPath rootPath = entry.getPath();
405 //                              if (rootPath.equals(resourcePath)) {
406 //                                      return project.getPackageFragmentRoot(file);
407 //                              }
408 //                      }
409 //              } catch (JavaModelException e) {
410 //              }
411 //              return null;
412 //      }
413         
414         /**
415          * Returns the package fragment root represented by the resource, or
416          * the package fragment the given resource is located in, or <code>null</code>
417          * if the given resource is not on the classpath of the given project.
418          */
419         public static IJavaElement determineIfOnClasspath(
420                 IResource resource,
421                 IJavaProject project) {
422                         
423                 IPath resourcePath = resource.getFullPath();
424                 try {
425                         IClasspathEntry[] entries = 
426                                 net.sourceforge.phpdt.internal.compiler.util.Util.isJavaFileName(resourcePath.lastSegment())
427                                         ? project.getRawClasspath() // JAVA file can only live inside SRC folder (on the raw path)
428                                         : ((JavaProject)project).getResolvedClasspath(true);
429                                 
430                         for (int i = 0; i < entries.length; i++) {
431                                 IClasspathEntry entry = entries[i];
432                                 if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) continue;
433                                 IPath rootPath = entry.getPath();
434                                 if (rootPath.equals(resourcePath)) {
435                                         return project.getPackageFragmentRoot(resource);
436                                 } else if (rootPath.isPrefixOf(resourcePath) && !Util.isExcluded(resource, ((ClasspathEntry)entry).fullExclusionPatternChars())) {
437                                         // given we have a resource child of the root, it cannot be a JAR pkg root
438                                         IPackageFragmentRoot root = ((JavaProject) project).getFolderPackageFragmentRoot(rootPath);
439                                         if (root == null) return null;
440                                         IPath pkgPath = resourcePath.removeFirstSegments(rootPath.segmentCount());
441                                         if (resource.getType() == IResource.FILE) {
442                                                 // if the resource is a file, then remove the last segment which
443                                                 // is the file name in the package
444                                                 pkgPath = pkgPath.removeLastSegments(1);
445                                                 
446                                                 // don't check validity of package name (see http://bugs.eclipse.org/bugs/show_bug.cgi?id=26706)
447 //                                              String pkgName = pkgPath.toString().replace('/', '.');
448                                                 String pkgName = pkgPath.toString();
449                                                 return root.getPackageFragment(pkgName);
450                                         } else {
451                                                 String pkgName = Util.packageName(pkgPath);
452                                                 if (pkgName == null){// || JavaConventions.validatePackageName(pkgName).getSeverity() == IStatus.ERROR) {
453                                                         return null;
454                                                 }
455                                                 return root.getPackageFragment(pkgName);
456                                         }
457                                 }
458                         }
459                 } catch (JavaModelException npe) {
460                         return null;
461                 }
462                 return null;
463         }
464         
465         /**
466          * The singleton manager
467          */
468         private final static JavaModelManager Manager= new JavaModelManager();
469
470         /**
471          * Infos cache.
472          */
473         protected JavaModelCache cache = new JavaModelCache();
474
475         /*
476          * Temporary cache of newly opened elements
477          */
478         private ThreadLocal temporaryCache = new ThreadLocal();
479         /**
480          * Set of elements which are out of sync with their buffers.
481          */
482         protected Map elementsOutOfSynchWithBuffers = new HashMap(11);
483         /**
484          * Holds the state used for delta processing.
485          */
486         public DeltaProcessingState deltaState = new DeltaProcessingState();
487         /**
488          * Turns delta firing on/off. By default it is on.
489          */
490         private boolean isFiring= true;
491
492         /**
493          * Queue of deltas created explicily by the Java Model that
494          * have yet to be fired.
495          */
496         ArrayList javaModelDeltas= new ArrayList();
497         /**
498          * Queue of reconcile deltas on working copies that have yet to be fired.
499          * This is a table form IWorkingCopy to IJavaElementDelta
500          */
501         HashMap reconcileDeltas = new HashMap();
502
503
504         /**
505          * Collection of listeners for Java element deltas
506          */
507         private IElementChangedListener[] elementChangedListeners = new IElementChangedListener[5];
508         private int[] elementChangedListenerMasks = new int[5];
509         private int elementChangedListenerCount = 0;
510         public int currentChangeEventType = ElementChangedEvent.PRE_AUTO_BUILD;
511         public static final int DEFAULT_CHANGE_EVENT = 0; // must not collide with ElementChangedEvent event masks
512
513
514
515         /**
516          * Used to convert <code>IResourceDelta</code>s into <code>IJavaElementDelta</code>s.
517          */
518 //      public final DeltaProcessor deltaProcessor = new DeltaProcessor(this);
519         /**
520          * Used to update the JavaModel for <code>IJavaElementDelta</code>s.
521          */
522         private final ModelUpdater modelUpdater =new ModelUpdater();
523         /**
524          * Workaround for bug 15168 circular errors not reported  
525          * This is a cache of the projects before any project addition/deletion has started.
526          */
527         public IJavaProject[] javaProjectsCache;
528
529         /**
530          * Table from IProject to PerProjectInfo.
531          * NOTE: this object itself is used as a lock to synchronize creation/removal of per project infos
532          */
533         protected Map perProjectInfo = new HashMap(5);
534         
535         /**
536          * A map from ICompilationUnit to IWorkingCopy
537          * of the shared working copies.
538          */
539         public Map sharedWorkingCopies = new HashMap();
540         
541         /**
542          * A weak set of the known scopes.
543          */
544         protected WeakHashMap scopes = new WeakHashMap();
545
546         public static class PerProjectInfo {
547                 public IProject project;
548                 public Object savedState;
549                 public boolean triedRead;
550                 public IClasspathEntry[] classpath;
551                 public IClasspathEntry[] lastResolvedClasspath;
552                 public Map resolvedPathToRawEntries; // reverse map from resolved path to raw entries
553                 public IPath outputLocation;
554                 public Preferences preferences;
555                 public PerProjectInfo(IProject project) {
556
557                         this.triedRead = false;
558                         this.savedState = null;
559                         this.project = project;
560                 }
561         }
562         public static class PerWorkingCopyInfo implements IProblemRequestor {
563                 int useCount = 0;
564                 IProblemRequestor problemRequestor;
565                 ICompilationUnit workingCopy;
566                 public PerWorkingCopyInfo(ICompilationUnit workingCopy, IProblemRequestor problemRequestor) {
567                         this.workingCopy = workingCopy;
568                         this.problemRequestor = problemRequestor;
569                 }
570                 public void acceptProblem(IProblem problem) {
571                         if (this.problemRequestor == null) return;
572                         this.problemRequestor.acceptProblem(problem);
573                 }
574                 public void beginReporting() {
575                         if (this.problemRequestor == null) return;
576                         this.problemRequestor.beginReporting();
577                 }
578                 public void endReporting() {
579                         if (this.problemRequestor == null) return;
580                         this.problemRequestor.endReporting();
581                 }
582                 public ICompilationUnit getWorkingCopy() {
583                         return this.workingCopy;
584                 }
585                 public boolean isActive() {
586                         return this.problemRequestor != null && this.problemRequestor.isActive();
587                 }
588                 public String toString() {
589                         StringBuffer buffer = new StringBuffer();
590                         buffer.append("Info for "); //$NON-NLS-1$
591                         buffer.append(((JavaElement)workingCopy).toStringWithAncestors());
592                         buffer.append("\nUse count = "); //$NON-NLS-1$
593                         buffer.append(this.useCount);
594                         buffer.append("\nProblem requestor:\n  "); //$NON-NLS-1$
595                         buffer.append(this.problemRequestor);
596                         return buffer.toString();
597                 }
598         }
599         public static boolean VERBOSE = false;
600         public static boolean CP_RESOLVE_VERBOSE = false;
601         public static boolean ZIP_ACCESS_VERBOSE = false;
602         
603         /**
604          * A cache of opened zip files per thread.
605          * (map from Thread to map of IPath to java.io.ZipFile)
606          * NOTE: this object itself is used as a lock to synchronize creation/removal of entries
607          */
608         private HashMap zipFiles = new HashMap();
609         
610         
611         /**
612          * Update the classpath variable cache
613          */
614         public static class PluginPreferencesListener implements Preferences.IPropertyChangeListener {
615                 /**
616                  * @see org.eclipse.core.runtime.Preferences.IPropertyChangeListener#propertyChange(PropertyChangeEvent)
617                  */
618                 public void propertyChange(Preferences.PropertyChangeEvent event) {
619 //                      TODO : jsurfer temp-del
620 //                      String propertyName = event.getProperty();
621 //                      if (propertyName.startsWith(CP_VARIABLE_PREFERENCES_PREFIX)) {
622 //                              String varName = propertyName.substring(CP_VARIABLE_PREFERENCES_PREFIX.length());
623 //                              String newValue = (String)event.getNewValue();
624 //                              if (newValue != null && !(newValue = newValue.trim()).equals(CP_ENTRY_IGNORE)) {
625 //                                      Variables.put(varName, new Path(newValue));
626 //                              } else {
627 //                                      Variables.remove(varName);
628 //                              }
629 //                      }
630 //                      if (propertyName.startsWith(CP_CONTAINER_PREFERENCES_PREFIX)) {
631 //                              recreatePersistedContainer(propertyName, (String)event.getNewValue(), false);
632 //                      }
633                 }
634         }
635
636         /**
637          * Line separator to use throughout the JavaModel for any source edit operation
638          */
639   public static String LINE_SEPARATOR = System.getProperty("line.separator"); //$NON-NLS-1$
640         /**
641          * Constructs a new JavaModelManager
642          */
643         private JavaModelManager() {
644         }
645
646         /**
647          * @deprecated - discard once debug has converted to not using it
648          */
649         public void addElementChangedListener(IElementChangedListener listener) {
650                 this.addElementChangedListener(listener, ElementChangedEvent.POST_CHANGE | ElementChangedEvent.POST_RECONCILE);
651         }
652         /**
653          * addElementChangedListener method comment.
654          * Need to clone defensively the listener information, in case some listener is reacting to some notification iteration by adding/changing/removing
655          * any of the other (for example, if it deregisters itself).
656          */
657         public void addElementChangedListener(IElementChangedListener listener, int eventMask) {
658                 for (int i = 0; i < this.elementChangedListenerCount; i++){
659                         if (this.elementChangedListeners[i].equals(listener)){
660                                 
661                                 // only clone the masks, since we could be in the middle of notifications and one listener decide to change
662                                 // any event mask of another listeners (yet not notified).
663                                 int cloneLength = this.elementChangedListenerMasks.length;
664                                 System.arraycopy(this.elementChangedListenerMasks, 0, this.elementChangedListenerMasks = new int[cloneLength], 0, cloneLength);
665                                 this.elementChangedListenerMasks[i] = eventMask; // could be different
666                                 return;
667                         }
668                 }
669                 // may need to grow, no need to clone, since iterators will have cached original arrays and max boundary and we only add to the end.
670                 int length;
671                 if ((length = this.elementChangedListeners.length) == this.elementChangedListenerCount){
672                         System.arraycopy(this.elementChangedListeners, 0, this.elementChangedListeners = new IElementChangedListener[length*2], 0, length);
673                         System.arraycopy(this.elementChangedListenerMasks, 0, this.elementChangedListenerMasks = new int[length*2], 0, length);
674                 }
675                 this.elementChangedListeners[this.elementChangedListenerCount] = listener;
676                 this.elementChangedListenerMasks[this.elementChangedListenerCount] = eventMask;
677                 this.elementChangedListenerCount++;
678         }
679
680         /**
681          * Starts caching ZipFiles.
682          * Ignores if there are already clients.
683          */
684         public void cacheZipFiles() {
685                 synchronized(this.zipFiles) {
686                         Thread currentThread = Thread.currentThread();
687                         if (this.zipFiles.get(currentThread) != null) return;
688                         this.zipFiles.put(currentThread, new HashMap());
689                 }
690         }
691         public void closeZipFile(ZipFile zipFile) {
692                 if (zipFile == null) return;
693                 synchronized(this.zipFiles) {
694                         if (this.zipFiles.get(Thread.currentThread()) != null) {
695                                 return; // zip file will be closed by call to flushZipFiles
696                         }
697                         try {
698                                 if (JavaModelManager.ZIP_ACCESS_VERBOSE) {
699                                         System.out.println("(" + Thread.currentThread() + ") [JavaModelManager.closeZipFile(ZipFile)] Closing ZipFile on " +zipFile.getName()); //$NON-NLS-1$   //$NON-NLS-2$
700                                 }
701                                 zipFile.close();
702                         } catch (IOException e) {
703                         }
704                 }
705         }
706         
707
708
709         /**
710          * Configure the plugin with respect to option settings defined in ".options" file
711          */
712         public void configurePluginDebugOptions(){
713                 if(JavaCore.getPlugin().isDebugging()){
714 //              TODO jsurfer temp-del
715                         
716                         String option = Platform.getDebugOption(BUILDER_DEBUG);
717 //                      if(option != null) JavaBuilder.DEBUG = option.equalsIgnoreCase("true") ; //$NON-NLS-1$
718 //                      
719 //                      option = Platform.getDebugOption(COMPILER_DEBUG);
720 //                      if(option != null) Compiler.DEBUG = option.equalsIgnoreCase("true") ; //$NON-NLS-1$
721 //
722 //                      option = Platform.getDebugOption(COMPLETION_DEBUG);
723 //                      if(option != null) CompletionEngine.DEBUG = option.equalsIgnoreCase("true") ; //$NON-NLS-1$
724 //                      
725                         option = Platform.getDebugOption(CP_RESOLVE_DEBUG);
726                         if(option != null) JavaModelManager.CP_RESOLVE_VERBOSE = option.equalsIgnoreCase("true") ; //$NON-NLS-1$
727
728                         option = Platform.getDebugOption(DELTA_DEBUG);
729                         if(option != null) DeltaProcessor.VERBOSE = option.equalsIgnoreCase("true") ; //$NON-NLS-1$
730
731 //                      option = Platform.getDebugOption(HIERARCHY_DEBUG);
732 //                      if(option != null) TypeHierarchy.DEBUG = option.equalsIgnoreCase("true") ; //$NON-NLS-1$
733 //
734 //                      option = Platform.getDebugOption(INDEX_MANAGER_DEBUG);
735 //                      if(option != null) IndexManager.VERBOSE = option.equalsIgnoreCase("true") ; //$NON-NLS-1$
736                         
737                         option = Platform.getDebugOption(JAVAMODEL_DEBUG);
738                         if(option != null) JavaModelManager.VERBOSE = option.equalsIgnoreCase("true") ; //$NON-NLS-1$
739
740                         option = Platform.getDebugOption(POST_ACTION_DEBUG);
741                         if(option != null) JavaModelOperation.POST_ACTION_VERBOSE = option.equalsIgnoreCase("true") ; //$NON-NLS-1$
742
743 //                      option = Platform.getDebugOption(SEARCH_DEBUG);
744 //                      if(option != null) SearchEngine.VERBOSE = option.equalsIgnoreCase("true") ; //$NON-NLS-1$
745 //
746 //                      option = Platform.getDebugOption(SELECTION_DEBUG);
747 //                      if(option != null) SelectionEngine.DEBUG = option.equalsIgnoreCase("true") ; //$NON-NLS-1$
748
749                         option = Platform.getDebugOption(ZIP_ACCESS_DEBUG);
750                         if(option != null) JavaModelManager.ZIP_ACCESS_VERBOSE = option.equalsIgnoreCase("true") ; //$NON-NLS-1$
751                 }
752         }
753         
754
755         /*
756          * Discards the per working copy info for the given working copy (making it a compilation unit)
757          * if its use count was 1. Otherwise, just decrement the use count.
758          * If the working copy is primary, computes the delta between its state and the original compilation unit
759          * and register it.
760          * Close the working copy, its buffer and remove it from the shared working copy table.
761          * Ignore if no per-working copy info existed.
762          * NOTE: it must be synchronized as it may interact with the element info cache (if useCount is decremented to 0), see bug 50667.
763          * Returns the new use count (or -1 if it didn't exist).
764          */
765         public synchronized int discardPerWorkingCopyInfo(CompilationUnit workingCopy) throws JavaModelException {
766                 synchronized(perWorkingCopyInfos) {
767                         WorkingCopyOwner owner = workingCopy.owner;
768                         Map workingCopyToInfos = (Map)this.perWorkingCopyInfos.get(owner);
769                         if (workingCopyToInfos == null) return -1;
770                         
771                         PerWorkingCopyInfo info = (PerWorkingCopyInfo)workingCopyToInfos.get(workingCopy);
772                         if (info == null) return -1;
773                         
774                         if (--info.useCount == 0) {
775                                 // create the delta builder (this remembers the current content of the working copy)
776                                 JavaElementDeltaBuilder deltaBuilder = null;
777                                 if (workingCopy.isPrimary()) {
778                                         deltaBuilder = new JavaElementDeltaBuilder(workingCopy);
779                                 }
780
781                                 // remove per working copy info
782                                 workingCopyToInfos.remove(workingCopy);
783                                 if (workingCopyToInfos.isEmpty()) {
784                                         this.perWorkingCopyInfos.remove(owner);
785                                 }
786
787                                 // remove infos + close buffer (since no longer working copy)
788                                 removeInfoAndChildren(workingCopy);
789                                 workingCopy.closeBuffer();
790
791                                 // compute the delta if needed and register it if there are changes
792                                 if (deltaBuilder != null) {
793                                         deltaBuilder.buildDeltas();
794                                         if ((deltaBuilder.delta != null) && (deltaBuilder.delta.getAffectedChildren().length > 0)) {
795                                                 getDeltaProcessor().registerJavaModelDelta(deltaBuilder.delta);
796                                         }
797                                 }
798                                 
799                         }
800                         return info.useCount;
801                 }
802         }
803         
804         /**
805          * @see ISaveParticipant
806          */
807         public void doneSaving(ISaveContext context){
808         }
809         
810         /**
811          * Fire Java Model delta, flushing them after the fact after post_change notification.
812          * If the firing mode has been turned off, this has no effect. 
813          */
814         public void fire(IJavaElementDelta customDelta, int eventType) {
815
816                 if (!this.isFiring) return;
817                 
818                 if (DeltaProcessor.VERBOSE && (eventType == DEFAULT_CHANGE_EVENT || eventType == ElementChangedEvent.PRE_AUTO_BUILD)) {
819                         System.out.println("-----------------------------------------------------------------------------------------------------------------------");//$NON-NLS-1$
820                 }
821
822                 IJavaElementDelta deltaToNotify;
823                 if (customDelta == null){
824                         deltaToNotify = this.mergeDeltas(this.javaModelDeltas);
825                 } else {
826                         deltaToNotify = customDelta;
827                 }
828                         
829                 // Refresh internal scopes
830                 if (deltaToNotify != null) {
831 //              TODO  temp-del
832 //                      Iterator scopes = this.scopes.keySet().iterator();
833 //                      while (scopes.hasNext()) {
834 //                              AbstractSearchScope scope = (AbstractSearchScope)scopes.next();
835 //                              scope.processDelta(deltaToNotify);
836 //                      }
837                 }
838                         
839                 // Notification
840         
841                 // Important: if any listener reacts to notification by updating the listeners list or mask, these lists will
842                 // be duplicated, so it is necessary to remember original lists in a variable (since field values may change under us)
843                 IElementChangedListener[] listeners = this.elementChangedListeners;
844                 int[] listenerMask = this.elementChangedListenerMasks;
845                 int listenerCount = this.elementChangedListenerCount;
846
847                 switch (eventType) {
848                         case DEFAULT_CHANGE_EVENT:
849                                 firePreAutoBuildDelta(deltaToNotify, listeners, listenerMask, listenerCount);
850                                 firePostChangeDelta(deltaToNotify, listeners, listenerMask, listenerCount);
851                                 fireReconcileDelta(listeners, listenerMask, listenerCount);
852                                 break;
853                         case ElementChangedEvent.PRE_AUTO_BUILD:
854                                 firePreAutoBuildDelta(deltaToNotify, listeners, listenerMask, listenerCount);
855                                 break;
856                         case ElementChangedEvent.POST_CHANGE:
857                                 firePostChangeDelta(deltaToNotify, listeners, listenerMask, listenerCount);
858                                 fireReconcileDelta(listeners, listenerMask, listenerCount);
859                                 break;
860                 }
861
862         }
863
864         private void firePreAutoBuildDelta(
865                 IJavaElementDelta deltaToNotify,
866                 IElementChangedListener[] listeners,
867                 int[] listenerMask,
868                 int listenerCount) {
869                         
870                 if (DeltaProcessor.VERBOSE){
871                         System.out.println("FIRING PRE_AUTO_BUILD Delta ["+Thread.currentThread()+"]:"); //$NON-NLS-1$//$NON-NLS-2$
872                         System.out.println(deltaToNotify == null ? "<NONE>" : deltaToNotify.toString()); //$NON-NLS-1$
873                 }
874                 if (deltaToNotify != null) {
875                         notifyListeners(deltaToNotify, ElementChangedEvent.PRE_AUTO_BUILD, listeners, listenerMask, listenerCount);
876                 }
877         }
878
879         private void firePostChangeDelta(
880                 IJavaElementDelta deltaToNotify,
881                 IElementChangedListener[] listeners,
882                 int[] listenerMask,
883                 int listenerCount) {
884                         
885                 // post change deltas
886                 if (DeltaProcessor.VERBOSE){
887                         System.out.println("FIRING POST_CHANGE Delta ["+Thread.currentThread()+"]:"); //$NON-NLS-1$//$NON-NLS-2$
888                         System.out.println(deltaToNotify == null ? "<NONE>" : deltaToNotify.toString()); //$NON-NLS-1$
889                 }
890                 if (deltaToNotify != null) {
891                         // flush now so as to keep listener reactions to post their own deltas for subsequent iteration
892                         this.flush();
893                         
894                         notifyListeners(deltaToNotify, ElementChangedEvent.POST_CHANGE, listeners, listenerMask, listenerCount);
895                 } 
896         }               
897         private void fireReconcileDelta(
898                 IElementChangedListener[] listeners,
899                 int[] listenerMask,
900                 int listenerCount) {
901
902
903                 IJavaElementDelta deltaToNotify = mergeDeltas(this.reconcileDeltas.values());
904                 if (DeltaProcessor.VERBOSE){
905                         System.out.println("FIRING POST_RECONCILE Delta ["+Thread.currentThread()+"]:"); //$NON-NLS-1$//$NON-NLS-2$
906                         System.out.println(deltaToNotify == null ? "<NONE>" : deltaToNotify.toString()); //$NON-NLS-1$
907                 }
908                 if (deltaToNotify != null) {
909                         // flush now so as to keep listener reactions to post their own deltas for subsequent iteration
910                         this.reconcileDeltas = new HashMap();
911                 
912                         notifyListeners(deltaToNotify, ElementChangedEvent.POST_RECONCILE, listeners, listenerMask, listenerCount);
913                 } 
914         }
915
916         public void notifyListeners(IJavaElementDelta deltaToNotify, int eventType, IElementChangedListener[] listeners, int[] listenerMask, int listenerCount) {
917                 final ElementChangedEvent extraEvent = new ElementChangedEvent(deltaToNotify, eventType);
918                 for (int i= 0; i < listenerCount; i++) {
919                         if ((listenerMask[i] & eventType) != 0){
920                                 final IElementChangedListener listener = listeners[i];
921                                 long start = -1;
922                                 if (DeltaProcessor.VERBOSE) {
923                                         System.out.print("Listener #" + (i+1) + "=" + listener.toString());//$NON-NLS-1$//$NON-NLS-2$
924                                         start = System.currentTimeMillis();
925                                 }
926                                 // wrap callbacks with Safe runnable for subsequent listeners to be called when some are causing grief
927                                 Platform.run(new ISafeRunnable() {
928                                         public void handleException(Throwable exception) {
929                                                 Util.log(exception, "Exception occurred in listener of Java element change notification"); //$NON-NLS-1$
930                                         }
931                                         public void run() throws Exception {
932                                                 listener.elementChanged(extraEvent);
933                                         }
934                                 });
935                                 if (DeltaProcessor.VERBOSE) {
936                                         System.out.println(" -> " + (System.currentTimeMillis()-start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
937                                 }
938                         }
939                 }
940         }
941         
942         /**
943          * Flushes all deltas without firing them.
944          */
945         protected void flush() {
946                 this.javaModelDeltas = new ArrayList();
947         }
948
949         /**
950          * Flushes ZipFiles cache if there are no more clients.
951          */
952         public void flushZipFiles() {
953                 synchronized(this.zipFiles) {
954                         Thread currentThread = Thread.currentThread();
955                         HashMap map = (HashMap)this.zipFiles.remove(currentThread);
956                         if (map == null) return;
957                         Iterator iterator = map.values().iterator();
958                         while (iterator.hasNext()) {
959                                 try {
960                                         ZipFile zipFile = (ZipFile)iterator.next();
961                                         if (JavaModelManager.ZIP_ACCESS_VERBOSE) {
962                                                 System.out.println("(" + currentThread + ") [JavaModelManager.flushZipFiles()] Closing ZipFile on " +zipFile.getName()); //$NON-NLS-1$//$NON-NLS-2$
963                                         }
964                                         zipFile.close();
965                                 } catch (IOException e) {
966                                 }
967                         }
968                 }       
969         }
970         
971
972         public DeltaProcessor getDeltaProcessor() {
973                 return this.deltaState.getDeltaProcessor();
974         }
975         /** 
976          * Returns the set of elements which are out of synch with their buffers.
977          */
978         protected Map getElementsOutOfSynchWithBuffers() {
979                 return this.elementsOutOfSynchWithBuffers;
980         }
981
982         /**
983          * Returns the <code>IJavaElement</code> represented by the 
984          * <code>String</code> memento.
985          */
986         public IJavaElement getHandleFromMemento(String memento) throws JavaModelException {
987                 if (memento == null) {
988                         return null;
989                 }
990                 JavaModel model= (JavaModel) getJavaModel();
991                 if (memento.equals("")){ // workspace memento //$NON-NLS-1$
992                         return model;
993                 }
994                 int modelEnd= memento.indexOf(JavaElement.JEM_JAVAPROJECT);
995                 if (modelEnd == -1) {
996                         return null;
997                 }
998                 boolean returnProject= false;
999                 int projectEnd= memento.indexOf(JavaElement.JEM_PACKAGEFRAGMENTROOT, modelEnd);
1000                 if (projectEnd == -1) {
1001                         projectEnd= memento.length();
1002                         returnProject= true;
1003                 }
1004                 String projectName= memento.substring(modelEnd + 1, projectEnd);
1005                 JavaProject proj= (JavaProject) model.getJavaProject(projectName);
1006                 if (returnProject) {
1007                         return proj;
1008                 }
1009                 int rootEnd= memento.indexOf(JavaElement.JEM_PACKAGEFRAGMENT, projectEnd + 1);
1010 //      TODO  temp-del
1011 //              if (rootEnd == -1) {
1012 //                      return model.getHandleFromMementoForRoot(memento, proj, projectEnd, memento.length());
1013 //              }
1014 //              IPackageFragmentRoot root = model.getHandleFromMementoForRoot(memento, proj, projectEnd, rootEnd);
1015 //              if (root == null)
1016 //                      return null;
1017 //
1018 //              int end= memento.indexOf(JavaElement.JEM_COMPILATIONUNIT, rootEnd);
1019 //              if (end == -1) {
1020 //                      end= memento.indexOf(JavaElement.JEM_CLASSFILE, rootEnd);
1021 //                      if (end == -1) {
1022 //                              if (rootEnd + 1 == memento.length()) {
1023 //                                      return root.getPackageFragment(IPackageFragment.DEFAULT_PACKAGE_NAME);
1024 //                              } else {
1025 //                                      return root.getPackageFragment(memento.substring(rootEnd + 1));
1026 //                              }
1027 //                      }
1028 //                      //deal with class file and binary members
1029 //                      return model.getHandleFromMementoForBinaryMembers(memento, root, rootEnd, end);
1030 //              }
1031 //
1032 //              //deal with compilation units and source members
1033 //              return model.getHandleFromMementoForSourceMembers(memento, root, rootEnd, end);
1034           return null;
1035         }
1036 //      public IndexManager getIndexManager() {
1037 //              return this.deltaProcessor.indexManager;
1038 //      }
1039
1040         /**
1041          *  Returns the info for the element.
1042          */
1043         public Object getInfo(IJavaElement element) {
1044                 return this.cache.getInfo(element);
1045         }
1046
1047         /**
1048          * Returns the handle to the active Java Model.
1049          */
1050         public final JavaModel getJavaModel() {
1051                 return javaModel;
1052         }
1053
1054         /**
1055          * Returns the singleton JavaModelManager
1056          */
1057         public final static JavaModelManager getJavaModelManager() {
1058                 return Manager;
1059         }
1060
1061         /**
1062          * Returns the last built state for the given project, or null if there is none.
1063          * Deserializes the state if necessary.
1064          *
1065          * For use by image builder and evaluation support only
1066          */
1067         public Object getLastBuiltState(IProject project, IProgressMonitor monitor) {
1068                 if (!JavaProject.hasJavaNature(project)) return null; // should never be requested on non-Java projects
1069                 PerProjectInfo info = getPerProjectInfo(project, true/*create if missing*/);
1070                 if (!info.triedRead) {
1071                         info.triedRead = true;
1072                         try {
1073                                 if (monitor != null)
1074                                         monitor.subTask(Util.bind("build.readStateProgress", project.getName())); //$NON-NLS-1$
1075                                 info.savedState = readState(project);
1076                         } catch (CoreException e) {
1077                                 e.printStackTrace();
1078                         }
1079                 }
1080                 return info.savedState;
1081         }
1082
1083         /*
1084          * Returns the per-project info for the given project. If specified, create the info if the info doesn't exist.
1085          */
1086         public PerProjectInfo getPerProjectInfo(IProject project, boolean create) {
1087                 synchronized(perProjectInfo) { // use the perProjectInfo collection as its own lock
1088                         PerProjectInfo info= (PerProjectInfo) perProjectInfo.get(project);
1089                         if (info == null && create) {
1090                                 info= new PerProjectInfo(project);
1091                                 perProjectInfo.put(project, info);
1092                         }
1093                         return info;
1094                 }
1095         }       
1096         
1097         /*
1098          * Returns  the per-project info for the given project.
1099          * If the info doesn't exist, check for the project existence and create the info.
1100          * @throws JavaModelException if the project doesn't exist.
1101          */
1102         public PerProjectInfo getPerProjectInfoCheckExistence(IProject project) throws JavaModelException {
1103                 JavaModelManager.PerProjectInfo info = getPerProjectInfo(project, false /* don't create info */);
1104                 if (info == null) {
1105                         if (!JavaProject.hasJavaNature(project)) {
1106                                 throw ((JavaProject)JavaCore.create(project)).newNotPresentException();
1107                         }
1108                         info = getPerProjectInfo(project, true /* create info */);
1109                 }
1110                 return info;
1111         }
1112         /*
1113          * Returns the per-working copy info for the given working copy at the given path.
1114          * If it doesn't exist and if create, add a new per-working copy info with the given problem requestor.
1115          * If recordUsage, increment the per-working copy info's use count.
1116          * Returns null if it doesn't exist and not create.
1117          */
1118         public PerWorkingCopyInfo getPerWorkingCopyInfo(CompilationUnit workingCopy,boolean create, boolean recordUsage, IProblemRequestor problemRequestor) {
1119                 synchronized(perWorkingCopyInfos) { // use the perWorkingCopyInfo collection as its own lock
1120                         WorkingCopyOwner owner = workingCopy.owner;
1121                         Map workingCopyToInfos = (Map)this.perWorkingCopyInfos.get(owner);
1122                         if (workingCopyToInfos == null && create) {
1123                                 workingCopyToInfos = new HashMap();
1124                                 this.perWorkingCopyInfos.put(owner, workingCopyToInfos);
1125                         }
1126
1127                         PerWorkingCopyInfo info = workingCopyToInfos == null ? null : (PerWorkingCopyInfo) workingCopyToInfos.get(workingCopy);
1128                         if (info == null && create) {
1129                                 info= new PerWorkingCopyInfo(workingCopy, problemRequestor);
1130                                 workingCopyToInfos.put(workingCopy, info);
1131                         }
1132                         if (info != null && recordUsage) info.useCount++;
1133                         return info;
1134                 }
1135         }       
1136         /**
1137          * Returns the name of the variables for which an CP variable initializer is registered through an extension point
1138          */
1139         public static String[] getRegisteredVariableNames(){
1140                 
1141                 Plugin jdtCorePlugin = JavaCore.getPlugin();
1142                 if (jdtCorePlugin == null) return null;
1143
1144                 ArrayList variableList = new ArrayList(5);
1145 //              IExtensionPoint extension = jdtCorePlugin.getDescriptor().getExtensionPoint(JavaModelManager.CPVARIABLE_INITIALIZER_EXTPOINT_ID);
1146 //              if (extension != null) {
1147 //                      IExtension[] extensions =  extension.getExtensions();
1148 //                      for(int i = 0; i < extensions.length; i++){
1149 //                              IConfigurationElement [] configElements = extensions[i].getConfigurationElements();
1150 //                              for(int j = 0; j < configElements.length; j++){
1151 //                                      String varAttribute = configElements[j].getAttribute("variable"); //$NON-NLS-1$
1152 //                                      if (varAttribute != null) variableList.add(varAttribute);
1153 //                              }
1154 //                      }       
1155 //              }
1156                 String[] variableNames = new String[variableList.size()];
1157                 variableList.toArray(variableNames);
1158                 return variableNames;
1159         }       
1160
1161         /**
1162          * Returns the name of the container IDs for which an CP container initializer is registered through an extension point
1163          */
1164 //      public static String[] getRegisteredContainerIDs(){
1165 //              
1166 //              Plugin jdtCorePlugin = PHPCore.getPlugin();
1167 //              if (jdtCorePlugin == null) return null;
1168 //
1169 //              ArrayList containerIDList = new ArrayList(5);
1170 //              IExtensionPoint extension = jdtCorePlugin.getDescriptor().getExtensionPoint(JavaModelManager.CPCONTAINER_INITIALIZER_EXTPOINT_ID);
1171 //              if (extension != null) {
1172 //                      IExtension[] extensions =  extension.getExtensions();
1173 //                      for(int i = 0; i < extensions.length; i++){
1174 //                              IConfigurationElement [] configElements = extensions[i].getConfigurationElements();
1175 //                              for(int j = 0; j < configElements.length; j++){
1176 //                                      String idAttribute = configElements[j].getAttribute("id"); //$NON-NLS-1$
1177 //                                      if (idAttribute != null) containerIDList.add(idAttribute);
1178 //                              }
1179 //                      }       
1180 //              }
1181 //              String[] containerIDs = new String[containerIDList.size()];
1182 //              containerIDList.toArray(containerIDs);
1183 //              return containerIDs;
1184 //      }       
1185
1186         /**
1187          * Returns the File to use for saving and restoring the last built state for the given project.
1188          */
1189         private File getSerializationFile(IProject project) {
1190                 if (!project.exists()) return null;
1191                 IPath workingLocation = project.getWorkingLocation(JavaCore.PLUGIN_ID);
1192                 return workingLocation.append("state.dat").toFile(); //$NON-NLS-1$
1193         }
1194         
1195         /*
1196          * Returns the temporary cache for newly opened elements for the current thread.
1197          * Creates it if not already created.
1198          */
1199         public HashMap getTemporaryCache() {
1200                 HashMap result = (HashMap)this.temporaryCache.get();
1201                 if (result == null) {
1202                         result = new HashMap();
1203                         this.temporaryCache.set(result);
1204                 }
1205                 return result;
1206         }
1207         /**
1208          * Returns the open ZipFile at the given location. If the ZipFile
1209          * does not yet exist, it is created, opened, and added to the cache
1210          * of open ZipFiles. The location must be a absolute path.
1211          *
1212          * @exception CoreException If unable to create/open the ZipFile
1213          */
1214         public ZipFile getZipFile(IPath path) throws CoreException {
1215                         
1216                 synchronized(this.zipFiles) { // TODO:  use PeThreadObject which does synchronization
1217                         Thread currentThread = Thread.currentThread();
1218                         HashMap map = null;
1219                         ZipFile zipFile;
1220                         if ((map = (HashMap)this.zipFiles.get(currentThread)) != null 
1221                                         && (zipFile = (ZipFile)map.get(path)) != null) {
1222                                         
1223                                 return zipFile;
1224                         }
1225                         String fileSystemPath= null;
1226                         IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
1227                         IResource file = root.findMember(path);
1228                         if (path.isAbsolute() && file != null) {
1229                                 if (file == null) { // external file
1230                                         fileSystemPath= path.toOSString();
1231                                 } else { // internal resource (not an IFile or not existing)
1232                                         IPath location;
1233                                         if (file.getType() != IResource.FILE || (location = file.getLocation()) == null) {
1234                                                 throw new CoreException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("file.notFound", path.toString()), null)); //$NON-NLS-1$
1235                                         }
1236                                         fileSystemPath= location.toOSString();
1237                                 }
1238                         } else if (!path.isAbsolute()) {
1239                                 file= root.getFile(path);
1240                                 if (file == null || file.getType() != IResource.FILE) {
1241                                         throw new CoreException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("file.notFound", path.toString()), null)); //$NON-NLS-1$
1242                                 }
1243                                 IPath location = file.getLocation();
1244                                 if (location == null) {
1245                                         throw new CoreException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("file.notFound", path.toString()), null)); //$NON-NLS-1$
1246                                 }
1247                                 fileSystemPath= location.toOSString();
1248                         } else {
1249                                 fileSystemPath= path.toOSString();
1250                         }
1251         
1252                         try {
1253                                 if (ZIP_ACCESS_VERBOSE) {
1254                                         System.out.println("(" + currentThread + ") [JavaModelManager.getZipFile(IPath)] Creating ZipFile on " + fileSystemPath ); //$NON-NLS-1$ //$NON-NLS-2$
1255                                 }
1256                                 zipFile = new ZipFile(fileSystemPath);
1257                                 if (map != null) {
1258                                         map.put(path, zipFile);
1259                                 }
1260                                 return zipFile;
1261                         } catch (IOException e) {
1262                                 throw new CoreException(new Status(Status.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("status.IOException"), e)); //$NON-NLS-1$
1263                         }
1264                 }
1265         }
1266         /*
1267          * Returns whether there is a temporary cache for the current thread.
1268          */
1269         public boolean hasTemporaryCache() {
1270                 return this.temporaryCache.get() != null;
1271         }
1272 //      public void loadVariablesAndContainers() throws CoreException {
1273 //
1274 //              // backward compatibility, consider persistent property 
1275 //              QualifiedName qName = new QualifiedName(PHPCore.PLUGIN_ID, "variables"); //$NON-NLS-1$
1276 //              String xmlString = ResourcesPlugin.getWorkspace().getRoot().getPersistentProperty(qName);
1277 //              
1278 //              try {
1279 //                      if (xmlString != null){
1280 //                              StringReader reader = new StringReader(xmlString);
1281 //                              Element cpElement;
1282 //                              try {
1283 //                                      DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
1284 //                                      cpElement = parser.parse(new InputSource(reader)).getDocumentElement();
1285 //                              } catch(SAXException e) {
1286 //                                      return;
1287 //                              } catch(ParserConfigurationException e){
1288 //                                      return;
1289 //                              } finally {
1290 //                                      reader.close();
1291 //                              }
1292 //                              if (cpElement == null) return;
1293 //                              if (!cpElement.getNodeName().equalsIgnoreCase("variables")) { //$NON-NLS-1$
1294 //                                      return;
1295 //                              }
1296 //                              
1297 //                              NodeList list= cpElement.getChildNodes();
1298 //                              int length= list.getLength();
1299 //                              for (int i= 0; i < length; ++i) {
1300 //                                      Node node= list.item(i);
1301 //                                      short type= node.getNodeType();
1302 //                                      if (type == Node.ELEMENT_NODE) {
1303 //                                              Element element= (Element) node;
1304 //                                              if (element.getNodeName().equalsIgnoreCase("variable")) { //$NON-NLS-1$
1305 //                                                      variablePut( 
1306 //                                                              element.getAttribute("name"), //$NON-NLS-1$
1307 //                                                              new Path(element.getAttribute("path"))); //$NON-NLS-1$
1308 //                                              }
1309 //                                      }
1310 //                              }
1311 //                      }
1312 //              } catch(IOException e){
1313 //              } finally {
1314 //                      if (xmlString != null){
1315 //                              ResourcesPlugin.getWorkspace().getRoot().setPersistentProperty(qName, null); // flush old one
1316 //                      }
1317 //                      
1318 //              }
1319 //              
1320 //              // load variables and containers from preferences into cache
1321 //              Preferences preferences = PHPeclipsePlugin.getDefault().getPluginPreferences();
1322
1323 //              // only get variable from preferences not set to their default
1324 //              String[] propertyNames = preferences.propertyNames();
1325 //              int variablePrefixLength = CP_VARIABLE_PREFERENCES_PREFIX.length();
1326 //              for (int i = 0; i < propertyNames.length; i++){
1327 //                      String propertyName = propertyNames[i];
1328 //                      if (propertyName.startsWith(CP_VARIABLE_PREFERENCES_PREFIX)){
1329 //                              String varName = propertyName.substring(variablePrefixLength);
1330 //                              IPath varPath = new Path(preferences.getString(propertyName).trim());
1331 //                              
1332 //                              Variables.put(varName, varPath); 
1333 //                              PreviousSessionVariables.put(varName, varPath);
1334 //                      }
1335 //                      if (propertyName.startsWith(CP_CONTAINER_PREFERENCES_PREFIX)){
1336 //                              recreatePersistedContainer(propertyName, preferences.getString(propertyName), true/*add to container values*/);
1337 //                      }
1338 //              }
1339 //              // override persisted values for variables which have a registered initializer
1340 //              String[] registeredVariables = getRegisteredVariableNames();
1341 //              for (int i = 0; i < registeredVariables.length; i++) {
1342 //                      String varName = registeredVariables[i];
1343 //                      Variables.put(varName, null); // reset variable, but leave its entry in the Map, so it will be part of variable names.
1344 //              }
1345 //              // override persisted values for containers which have a registered initializer
1346 //              String[] registeredContainerIDs = getRegisteredContainerIDs();
1347 //              for (int i = 0; i < registeredContainerIDs.length; i++) {
1348 //                      String containerID = registeredContainerIDs[i];
1349 //                      Iterator projectIterator = Containers.keySet().iterator();
1350 //                      while (projectIterator.hasNext()){
1351 //                              IJavaProject project = (IJavaProject)projectIterator.next();
1352 //                              Map projectContainers = (Map)Containers.get(project);
1353 //                              if (projectContainers != null){
1354 //                                      Iterator containerIterator = projectContainers.keySet().iterator();
1355 //                                      while (containerIterator.hasNext()){
1356 //                                              IPath containerPath = (IPath)containerIterator.next();
1357 //                                              if (containerPath.segment(0).equals(containerID)) { // registered container
1358 //                                                      projectContainers.put(containerPath, null); // reset container value, but leave entry in Map
1359 //                                              }
1360 //                                      }
1361 //                              }
1362 //                      }
1363 //              }
1364 //      }
1365
1366         /**
1367          * Merged all awaiting deltas.
1368          */
1369         public IJavaElementDelta mergeDeltas(Collection deltas) {
1370                 if (deltas.size() == 0) return null;
1371                 if (deltas.size() == 1) return (IJavaElementDelta)deltas.iterator().next();
1372                 
1373                 if (DeltaProcessor.VERBOSE) {
1374                         System.out.println("MERGING " + deltas.size() + " DELTAS ["+Thread.currentThread()+"]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
1375                 }
1376                 
1377                 Iterator iterator = deltas.iterator();
1378                 IJavaElement javaModel = this.getJavaModel();
1379                 JavaElementDelta rootDelta = new JavaElementDelta(javaModel);
1380                 boolean insertedTree = false;
1381                 while (iterator.hasNext()) {
1382                         JavaElementDelta delta = (JavaElementDelta)iterator.next();
1383                         if (DeltaProcessor.VERBOSE) {
1384                                 System.out.println(delta.toString());
1385                         }
1386                         IJavaElement element = delta.getElement();
1387                         if (javaModel.equals(element)) {
1388                                 IJavaElementDelta[] children = delta.getAffectedChildren();
1389                                 for (int j = 0; j < children.length; j++) {
1390                                         JavaElementDelta projectDelta = (JavaElementDelta) children[j];
1391                                         rootDelta.insertDeltaTree(projectDelta.getElement(), projectDelta);
1392                                         insertedTree = true;
1393                                 }
1394                                 IResourceDelta[] resourceDeltas = delta.getResourceDeltas();
1395                                 if (resourceDeltas != null) {
1396                                         for (int i = 0, length = resourceDeltas.length; i < length; i++) {
1397                                                 rootDelta.addResourceDelta(resourceDeltas[i]);
1398                                                 insertedTree = true;
1399                                         }
1400                                 }
1401                         } else {
1402                                 rootDelta.insertDeltaTree(element, delta);
1403                                 insertedTree = true;
1404                         }
1405                 }
1406                 if (insertedTree) {
1407                         return rootDelta;
1408                 }
1409                 else {
1410                         return null;
1411                 }
1412         }       
1413
1414         /**
1415          *  Returns the info for this element without
1416          *  disturbing the cache ordering.
1417          */ // TODO: should be synchronized, could answer unitialized info or if cache is in middle of rehash, could even answer distinct element info
1418         protected Object peekAtInfo(IJavaElement element) {
1419                 return this.cache.peekAtInfo(element);
1420         }
1421
1422         /**
1423          * @see ISaveParticipant
1424          */
1425         public void prepareToSave(ISaveContext context) throws CoreException {
1426         }
1427         
1428         protected void putInfo(IJavaElement element, Object info) {
1429                 this.cache.putInfo(element, info);
1430         }
1431         /*
1432          * Puts the infos in the given map (keys are IJavaElements and values are JavaElementInfos)
1433          * in the Java model cache in an atomic way.
1434          * First checks that the info for the opened element (or one of its ancestors) has not been 
1435          * added to the cache. If it is the case, another thread has opened the element (or one of
1436          * its ancestors). So returns without updating the cache.
1437          */
1438         protected synchronized void putInfos(IJavaElement openedElement, Map newElements) {
1439                 // remove children
1440                 Object existingInfo = this.cache.peekAtInfo(openedElement);
1441                 if (openedElement instanceof IParent && existingInfo instanceof JavaElementInfo) {
1442                         IJavaElement[] children = ((JavaElementInfo)existingInfo).getChildren();
1443                         for (int i = 0, size = children.length; i < size; ++i) {
1444                                 JavaElement child = (JavaElement) children[i];
1445                                 try {
1446                                         child.close();
1447                                 } catch (JavaModelException e) {
1448                                         // ignore
1449                                 }
1450                         }
1451                 }
1452         
1453                 Iterator iterator = newElements.keySet().iterator();
1454                 while (iterator.hasNext()) {
1455                         IJavaElement element = (IJavaElement)iterator.next();
1456                         Object info = newElements.get(element);
1457                         this.cache.putInfo(element, info);
1458                 }
1459         }
1460         /**
1461          * Reads the build state for the relevant project.
1462          */
1463         protected Object readState(IProject project) throws CoreException {
1464                 File file = getSerializationFile(project);
1465                 if (file != null && file.exists()) {
1466                         try {
1467                                 DataInputStream in= new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
1468                                 try {
1469                                         String pluginID= in.readUTF();
1470                                         if (!pluginID.equals(JavaCore.PLUGIN_ID))
1471                                                 throw new IOException(Util.bind("build.wrongFileFormat")); //$NON-NLS-1$
1472                                         String kind= in.readUTF();
1473                                         if (!kind.equals("STATE")) //$NON-NLS-1$
1474                                                 throw new IOException(Util.bind("build.wrongFileFormat")); //$NON-NLS-1$
1475                                         if (in.readBoolean())
1476                                                 return PHPBuilder.readState(project, in);
1477                                         if (PHPBuilder.DEBUG)
1478                                                 System.out.println("Saved state thinks last build failed for " + project.getName()); //$NON-NLS-1$
1479                                 } finally {
1480                                         in.close();
1481                                 }
1482                         } catch (Exception e) {
1483                                 e.printStackTrace();
1484                                 throw new CoreException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, Platform.PLUGIN_ERROR, "Error reading last build state for project "+ project.getName(), e)); //$NON-NLS-1$
1485                         }
1486                 }
1487                 return null;
1488         }
1489
1490 //      public static void recreatePersistedContainer(String propertyName, String containerString, boolean addToContainerValues) {
1491 //              int containerPrefixLength = CP_CONTAINER_PREFERENCES_PREFIX.length();
1492 //              int index = propertyName.indexOf('|', containerPrefixLength);
1493 //              if (containerString != null) containerString = containerString.trim();
1494 //              if (index > 0) {
1495 //                      final String projectName = propertyName.substring(containerPrefixLength, index).trim();
1496 //                      JavaProject project = (JavaProject)getJavaModelManager().getJavaModel().getJavaProject(projectName);
1497 //                      final IPath containerPath = new Path(propertyName.substring(index+1).trim());
1498 //                      
1499 //                      if (containerString == null || containerString.equals(CP_ENTRY_IGNORE)) {
1500 //                              containerPut(project, containerPath, null);
1501 //                      } else {
1502 //                              final IClasspathEntry[] containerEntries = project.decodeClasspath(containerString, false, false);
1503 //                              if (containerEntries != null && containerEntries != JavaProject.INVALID_CLASSPATH) {
1504 //                                      IClasspathContainer container = new IClasspathContainer() {
1505 //                                              public IClasspathEntry[] getClasspathEntries() {
1506 //                                                      return containerEntries;
1507 //                                              }
1508 //                                              public String getDescription() {
1509 //                                                      return "Persisted container ["+containerPath+" for project ["+ projectName+"]"; //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
1510 //                                              }
1511 //                                              public int getKind() {
1512 //                                                      return 0; 
1513 //                                              }
1514 //                                              public IPath getPath() {
1515 //                                                      return containerPath;
1516 //                                              }
1517 //                                              public String toString() {
1518 //                                                      return getDescription();
1519 //                                              }
1520 //
1521 //                                      };
1522 //                                      if (addToContainerValues) {
1523 //                                              containerPut(project, containerPath, container);
1524 //                                      }
1525 //                                      Map projectContainers = (Map)PreviousSessionContainers.get(project);
1526 //                                      if (projectContainers == null){
1527 //                                              projectContainers = new HashMap(1);
1528 //                                              PreviousSessionContainers.put(project, projectContainers);
1529 //                                      }
1530 //                                      projectContainers.put(containerPath, container);
1531 //                              }
1532 //                      }
1533 //              }
1534 //      }
1535
1536         /**
1537          * Registers the given delta with this manager.
1538          */
1539         protected void registerJavaModelDelta(IJavaElementDelta delta) {
1540                 this.javaModelDeltas.add(delta);
1541         }
1542         
1543         /**
1544          * Remembers the given scope in a weak set
1545          * (so no need to remove it: it will be removed by the garbage collector)
1546          */
1547 //      public void rememberScope(AbstractSearchScope scope) {
1548 //              // NB: The value has to be null so as to not create a strong reference on the scope
1549 //              this.scopes.put(scope, null); 
1550 //      }
1551
1552         /**
1553          * removeElementChangedListener method comment.
1554          */
1555         public void removeElementChangedListener(IElementChangedListener listener) {
1556                 
1557                 for (int i = 0; i < this.elementChangedListenerCount; i++){
1558                         
1559                         if (this.elementChangedListeners[i].equals(listener)){
1560                                 
1561                                 // need to clone defensively since we might be in the middle of listener notifications (#fire)
1562                                 int length = this.elementChangedListeners.length;
1563                                 IElementChangedListener[] newListeners = new IElementChangedListener[length];
1564                                 System.arraycopy(this.elementChangedListeners, 0, newListeners, 0, i);
1565                                 int[] newMasks = new int[length];
1566                                 System.arraycopy(this.elementChangedListenerMasks, 0, newMasks, 0, i);
1567                                 
1568                                 // copy trailing listeners
1569                                 int trailingLength = this.elementChangedListenerCount - i - 1;
1570                                 if (trailingLength > 0){
1571                                         System.arraycopy(this.elementChangedListeners, i+1, newListeners, i, trailingLength);
1572                                         System.arraycopy(this.elementChangedListenerMasks, i+1, newMasks, i, trailingLength);
1573                                 }
1574                                 
1575                                 // update manager listener state (#fire need to iterate over original listeners through a local variable to hold onto
1576                                 // the original ones)
1577                                 this.elementChangedListeners = newListeners;
1578                                 this.elementChangedListenerMasks = newMasks;
1579                                 this.elementChangedListenerCount--;
1580                                 return;
1581                         }
1582                 }
1583         }
1584         
1585 //      PROTECTED VOID REMOVEINFO(IJAVAELEMENT ELEMENT) {
1586 //              THIS.CACHE.REMOVEINFO(ELEMENT);
1587 //      }
1588         /*
1589          * Removes all cached info for the given element (including all children)
1590          * from the cache.
1591          * Returns the info for the given element, or null if it was closed.
1592          */
1593         public synchronized Object removeInfoAndChildren(JavaElement element) throws JavaModelException {
1594                 Object info = this.cache.peekAtInfo(element);
1595                 if (info != null) {
1596                         boolean wasVerbose = false;
1597                         try {
1598                                 if (VERBOSE) {
1599                                         System.out.println("CLOSING Element ("+ Thread.currentThread()+"): " + element.toStringWithAncestors());  //$NON-NLS-1$//$NON-NLS-2$
1600                                         wasVerbose = true;
1601                                         VERBOSE = false;
1602                                 }
1603                                 element.closing(info);
1604                                 if (element instanceof IParent && info instanceof JavaElementInfo) {
1605                                         IJavaElement[] children = ((JavaElementInfo)info).getChildren();
1606                                         for (int i = 0, size = children.length; i < size; ++i) {
1607                                                 JavaElement child = (JavaElement) children[i];
1608                                                 child.close();
1609                                         }
1610                                 }
1611                                 this.cache.removeInfo(element);
1612                                 if (wasVerbose) {
1613                                         System.out.println("-> Package cache size = " + this.cache.pkgSize()); //$NON-NLS-1$
1614                                         System.out.println("-> Openable cache filling ratio = " + NumberFormat.getInstance().format(this.cache.openableFillingRatio()) + "%"); //$NON-NLS-1$//$NON-NLS-2$
1615                                 }
1616                         } finally {
1617                                 JavaModelManager.VERBOSE = wasVerbose;
1618                         }
1619                         return info;
1620                 }
1621                 return null;
1622         }       
1623         public void removePerProjectInfo(JavaProject javaProject) {
1624                 synchronized(perProjectInfo) { // use the perProjectInfo collection as its own lock
1625                         IProject project = javaProject.getProject();
1626                         PerProjectInfo info= (PerProjectInfo) perProjectInfo.get(project);
1627                         if (info != null) {
1628                                 perProjectInfo.remove(project);
1629                         }
1630                 }
1631         }
1632         /*
1633          * Resets the temporary cache for newly created elements to null.
1634          */
1635         public void resetTemporaryCache() {
1636                 this.temporaryCache.set(null);
1637         }
1638         /**
1639          * @see ISaveParticipant
1640          */
1641         public void rollback(ISaveContext context){
1642         }
1643
1644         private void saveState(PerProjectInfo info, ISaveContext context) throws CoreException {
1645
1646                 // passed this point, save actions are non trivial
1647                 if (context.getKind() == ISaveContext.SNAPSHOT) return;
1648                 
1649                 // save built state
1650                 if (info.triedRead) saveBuiltState(info);
1651         }
1652         
1653         /**
1654          * Saves the built state for the project.
1655          */
1656         private void saveBuiltState(PerProjectInfo info) throws CoreException {
1657                 if (PHPBuilder.DEBUG)
1658                         System.out.println(Util.bind("build.saveStateProgress", info.project.getName())); //$NON-NLS-1$
1659                 File file = getSerializationFile(info.project);
1660                 if (file == null) return;
1661                 long t = System.currentTimeMillis();
1662                 try {
1663                         DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
1664                         try {
1665                                 out.writeUTF(JavaCore.PLUGIN_ID);
1666                                 out.writeUTF("STATE"); //$NON-NLS-1$
1667                                 if (info.savedState == null) {
1668                                         out.writeBoolean(false);
1669                                 } else {
1670                                         out.writeBoolean(true);
1671                                         PHPBuilder.writeState(info.savedState, out);
1672                                 }
1673                         } finally {
1674                                 out.close();
1675                         }
1676                 } catch (RuntimeException e) {
1677                         try {file.delete();} catch(SecurityException se) {}
1678                         throw new CoreException(
1679                                 new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, Platform.PLUGIN_ERROR,
1680                                         Util.bind("build.cannotSaveState", info.project.getName()), e)); //$NON-NLS-1$
1681                 } catch (IOException e) {
1682                         try {file.delete();} catch(SecurityException se) {}
1683                         throw new CoreException(
1684                                 new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, Platform.PLUGIN_ERROR,
1685                                         Util.bind("build.cannotSaveState", info.project.getName()), e)); //$NON-NLS-1$
1686                 }
1687                 if (PHPBuilder.DEBUG) {
1688                         t = System.currentTimeMillis() - t;
1689                         System.out.println(Util.bind("build.saveStateComplete", String.valueOf(t))); //$NON-NLS-1$
1690                 }
1691         }
1692
1693         /**
1694          * @see ISaveParticipant
1695          */
1696         public void saving(ISaveContext context) throws CoreException {
1697         
1698                 IProject savedProject = context.getProject();
1699                 if (savedProject != null) {
1700                         if (!JavaProject.hasJavaNature(savedProject)) return; // ignore
1701                         PerProjectInfo info = getPerProjectInfo(savedProject, true /* create info */);
1702                         saveState(info, context);
1703                         return;
1704                 }
1705
1706                 ArrayList vStats= null; // lazy initialized
1707                 for (Iterator iter =  perProjectInfo.values().iterator(); iter.hasNext();) {
1708                         try {
1709                                 PerProjectInfo info = (PerProjectInfo) iter.next();
1710                                 saveState(info, context);
1711                         } catch (CoreException e) {
1712                                 if (vStats == null)
1713                                         vStats= new ArrayList();
1714                                 vStats.add(e.getStatus());
1715                         }
1716                 }
1717                 if (vStats != null) {
1718                         IStatus[] stats= new IStatus[vStats.size()];
1719                         vStats.toArray(stats);
1720                         throw new CoreException(new MultiStatus(JavaCore.PLUGIN_ID, IStatus.ERROR, stats, Util.bind("build.cannotSaveStates"), null)); //$NON-NLS-1$
1721                 }
1722         }
1723
1724         /**
1725          * Record the order in which to build the java projects (batch build). This order is based
1726          * on the projects classpath settings.
1727          */
1728         protected void setBuildOrder(String[] javaBuildOrder) throws JavaModelException {
1729
1730                 // optional behaviour
1731                 // possible value of index 0 is Compute
1732                 if (!JavaCore.COMPUTE.equals(JavaCore.getOption(JavaCore.CORE_JAVA_BUILD_ORDER))) return; // cannot be customized at project level
1733                 
1734                 if (javaBuildOrder == null || javaBuildOrder.length <= 1) return;
1735                 
1736                 IWorkspace workspace = ResourcesPlugin.getWorkspace();
1737                 IWorkspaceDescription description = workspace.getDescription();
1738                 String[] wksBuildOrder = description.getBuildOrder();
1739
1740                 String[] newOrder;
1741                 if (wksBuildOrder == null){
1742                         newOrder = javaBuildOrder;
1743                 } else {
1744                         // remove projects which are already mentionned in java builder order
1745                         int javaCount = javaBuildOrder.length;
1746                         HashMap newSet = new HashMap(javaCount); // create a set for fast check
1747                         for (int i = 0; i < javaCount; i++){
1748                                 newSet.put(javaBuildOrder[i], javaBuildOrder[i]);
1749                         }
1750                         int removed = 0;
1751                         int oldCount = wksBuildOrder.length;
1752                         for (int i = 0; i < oldCount; i++){
1753                                 if (newSet.containsKey(wksBuildOrder[i])){
1754                                         wksBuildOrder[i] = null;
1755                                         removed++;
1756                                 }
1757                         }
1758                         // add Java ones first
1759                         newOrder = new String[oldCount - removed + javaCount];
1760                         System.arraycopy(javaBuildOrder, 0, newOrder, 0, javaCount); // java projects are built first
1761
1762                         // copy previous items in their respective order
1763                         int index = javaCount;
1764                         for (int i = 0; i < oldCount; i++){
1765                                 if (wksBuildOrder[i] != null){
1766                                         newOrder[index++] = wksBuildOrder[i];
1767                                 }
1768                         }
1769                 }
1770                 // commit the new build order out
1771                 description.setBuildOrder(newOrder);
1772                 try {
1773                         workspace.setDescription(description);
1774                 } catch(CoreException e){
1775                         throw new JavaModelException(e);
1776                 }
1777         }
1778
1779         /**
1780          * Sets the last built state for the given project, or null to reset it.
1781          */
1782         public void setLastBuiltState(IProject project, Object state) {
1783                 if (!JavaProject.hasJavaNature(project)) return; // should never be requested on non-Java projects
1784                 PerProjectInfo info = getPerProjectInfo(project, true /*create if missing*/);
1785                 info.triedRead = true; // no point trying to re-read once using setter
1786                 info.savedState = state;
1787                 if (state == null) { // delete state file to ensure a full build happens if the workspace crashes
1788                         try {
1789                                 File file = getSerializationFile(project);
1790                                 if (file != null && file.exists())
1791                                         file.delete();
1792                         } catch(SecurityException se) {}
1793                 }
1794         }
1795
1796         public void shutdown () {
1797 //      TODO  temp-del
1798 //              if (this.deltaProcessor.indexManager != null){ // no more indexing
1799 //                      this.deltaProcessor.indexManager.shutdown();
1800 //              }
1801                 try {
1802                         IJavaModel model = this.getJavaModel();
1803                         if (model != null) {
1804
1805                                 model.close();
1806                         }
1807                 } catch (JavaModelException e) {
1808                 }
1809         }
1810
1811         /**
1812          * Turns the firing mode to on. That is, deltas that are/have been
1813          * registered will be fired.
1814          */
1815         public void startDeltas() {
1816                 this.isFiring= true;
1817         }
1818
1819         /**
1820          * Turns the firing mode to off. That is, deltas that are/have been
1821          * registered will not be fired until deltas are started again.
1822          */
1823         public void stopDeltas() {
1824                 this.isFiring= false;
1825         }
1826         
1827         /**
1828          * Update Java Model given some delta
1829          */
1830         public void updateJavaModel(IJavaElementDelta customDelta) {
1831
1832                 if (customDelta == null){
1833                         for (int i = 0, length = this.javaModelDeltas.size(); i < length; i++){
1834                                 IJavaElementDelta delta = (IJavaElementDelta)this.javaModelDeltas.get(i);
1835                                 this.modelUpdater.processJavaDelta(delta);
1836                         }
1837                 } else {
1838                         this.modelUpdater.processJavaDelta(customDelta);
1839                 }
1840         }
1841
1842
1843         
1844         public static IPath variableGet(String variableName){
1845                 return (IPath)Variables.get(variableName);
1846         }
1847
1848         public static String[] variableNames(){
1849                 int length = Variables.size();
1850                 String[] result = new String[length];
1851                 Iterator vars = Variables.keySet().iterator();
1852                 int index = 0;
1853                 while (vars.hasNext()) {
1854                         result[index++] = (String) vars.next();
1855                 }
1856                 return result;
1857         }
1858         
1859         public static void variablePut(String variableName, IPath variablePath){                
1860
1861                 // update cache - do not only rely on listener refresh          
1862                 if (variablePath == null) {
1863                         Variables.remove(variableName);
1864                         PreviousSessionVariables.remove(variableName);
1865                 } else {
1866                         Variables.put(variableName, variablePath);
1867                 }
1868
1869                 // do not write out intermediate initialization value
1870                 if (variablePath == JavaModelManager.VariableInitializationInProgress){
1871                         return;
1872                 } 
1873                 Preferences preferences = JavaCore.getPlugin().getPluginPreferences();
1874                 String variableKey = CP_VARIABLE_PREFERENCES_PREFIX+variableName;
1875                 String variableString = variablePath == null ? CP_ENTRY_IGNORE : variablePath.toString();
1876                 preferences.setDefault(variableKey, CP_ENTRY_IGNORE); // use this default to get rid of removed ones
1877                 preferences.setValue(variableKey, variableString);
1878                 JavaCore.getPlugin().savePluginPreferences();
1879         }
1880         /*
1881          * Returns all the working copies which have the given owner.
1882          * Adds the working copies of the primary owner if specified.
1883          * Returns null if it has none.
1884          */
1885         public ICompilationUnit[] getWorkingCopies(WorkingCopyOwner owner, boolean addPrimary) {
1886                 synchronized(perWorkingCopyInfos) {
1887                         ICompilationUnit[] primaryWCs = addPrimary && owner != DefaultWorkingCopyOwner.PRIMARY 
1888                                 ? getWorkingCopies(DefaultWorkingCopyOwner.PRIMARY, false) 
1889                                 : null;
1890                         Map workingCopyToInfos = (Map)perWorkingCopyInfos.get(owner);
1891                         if (workingCopyToInfos == null) return primaryWCs;
1892                         int primaryLength = primaryWCs == null ? 0 : primaryWCs.length;
1893                         int size = workingCopyToInfos.size(); // note size is > 0 otherwise pathToPerWorkingCopyInfos would be null
1894                         ICompilationUnit[] result = new ICompilationUnit[primaryLength + size];
1895                         if (primaryWCs != null) {
1896                                 System.arraycopy(primaryWCs, 0, result, 0, primaryLength);
1897                         }
1898                         Iterator iterator = workingCopyToInfos.values().iterator();
1899                         int index = primaryLength;
1900                         while(iterator.hasNext()) {
1901                                 result[index++] = ((JavaModelManager.PerWorkingCopyInfo)iterator.next()).getWorkingCopy();
1902                         }
1903                         return result;
1904                 }               
1905         }
1906 }