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