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