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