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