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