new version with WorkingCopy Management
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / core / JavaElement.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.util.ArrayList;
14
15 import net.sourceforge.phpdt.core.ICompilationUnit;
16 import net.sourceforge.phpdt.core.IJavaElement;
17 import net.sourceforge.phpdt.core.IJavaModel;
18 import net.sourceforge.phpdt.core.IJavaModelStatusConstants;
19 import net.sourceforge.phpdt.core.IJavaProject;
20 import net.sourceforge.phpdt.core.IOpenable;
21 import net.sourceforge.phpdt.core.IParent;
22 import net.sourceforge.phpdt.core.ISourceRange;
23 import net.sourceforge.phpdt.core.ISourceReference;
24 import net.sourceforge.phpdt.core.JavaModelException;
25 import net.sourceforge.phpdt.core.jdom.IDOMCompilationUnit;
26 import net.sourceforge.phpdt.core.jdom.IDOMNode;
27 import net.sourceforge.phpdt.internal.corext.Assert;
28
29 import org.eclipse.core.resources.IResourceStatus;
30 import org.eclipse.core.resources.ResourcesPlugin;
31 import org.eclipse.core.runtime.CoreException;
32 import org.eclipse.core.runtime.IProgressMonitor;
33 import org.eclipse.core.runtime.Path;
34 import org.eclipse.core.runtime.PlatformObject;
35
36 /**
37  * Root of Java element handle hierarchy.
38  *
39  * @see IJavaElement
40  */
41 public abstract class JavaElement extends PlatformObject implements IJavaElement {
42
43         public static final char JEM_JAVAPROJECT= '=';
44         public static final char JEM_PACKAGEFRAGMENTROOT= Path.SEPARATOR;
45         public static final char JEM_PACKAGEFRAGMENT= '<';
46         public static final char JEM_FIELD= '^';
47         public static final char JEM_METHOD= '~';
48         public static final char JEM_INITIALIZER= '|';
49         public static final char JEM_COMPILATIONUNIT= '{';
50         public static final char JEM_CLASSFILE= '(';
51         public static final char JEM_TYPE= '[';
52         public static final char JEM_PACKAGEDECLARATION= '%';
53         public static final char JEM_IMPORTDECLARATION= '#';
54
55         /**
56          * A count to uniquely identify this element in the case
57          * that a duplicate named element exists. For example, if
58          * there are two fields in a compilation unit with the
59          * same name, the occurrence count is used to distinguish
60          * them.  The occurrence count starts at 1 (thus the first 
61          * occurrence is occurrence 1, not occurrence 0).
62          */
63         protected int fOccurrenceCount = 1;
64
65
66         /**
67          * This element's type - one of the constants defined
68          * in IJavaLanguageElementTypes.
69          */
70         protected int fLEType = 0;
71
72         /**
73          * This element's parent, or <code>null</code> if this
74          * element does not have a parent.
75          */
76         protected IJavaElement fParent;
77
78         /**
79          * This element's name, or an empty <code>String</code> if this
80          * element does not have a name.
81          */
82         protected String fName;
83
84         protected static final Object NO_INFO = new Object();
85         
86         /**
87          * Constructs a handle for a java element of the specified type, with
88          * the given parent element and name.
89          *
90          * @param type - one of the constants defined in IJavaLanguageElement
91          *
92          * @exception IllegalArgumentException if the type is not one of the valid
93          *              Java element type constants
94          *
95          */
96         protected JavaElement(int type, IJavaElement parent, String name) throws IllegalArgumentException {
97                 if (type < JAVA_MODEL || type > IMPORT_DECLARATION) {
98                         throw new IllegalArgumentException(Util.bind("element.invalidType")); //$NON-NLS-1$
99                 }
100                 fLEType= type;
101                 fParent= parent;
102                 fName= name;
103         }
104         /**
105          * @see IOpenable
106          */
107         public void close() throws JavaModelException {
108                 Object info = JavaModelManager.getJavaModelManager().peekAtInfo(this);
109                 if (info != null) {
110                         boolean wasVerbose = false;
111                         try {
112                                 if (JavaModelManager.VERBOSE) {
113                                         System.out.println("CLOSING Element ("+ Thread.currentThread()+"): " + this.toStringWithAncestors());  //$NON-NLS-1$//$NON-NLS-2$
114                                         wasVerbose = true;
115                                         JavaModelManager.VERBOSE = false;
116                                 }
117                                 if (this instanceof IParent) {
118                                         IJavaElement[] children = ((JavaElementInfo) info).getChildren();
119                                         for (int i = 0, size = children.length; i < size; ++i) {
120                                                 JavaElement child = (JavaElement) children[i];
121                                                 child.close();
122                                         }
123                                 }
124                                 closing(info);
125                                 JavaModelManager.getJavaModelManager().removeInfo(this);
126                                 if (wasVerbose) {
127                                         System.out.println("-> Package cache size = " + JavaModelManager.getJavaModelManager().cache.pkgSize()); //$NON-NLS-1$
128                                         System.out.println("-> Openable cache filling ratio = " + JavaModelManager.getJavaModelManager().cache.openableFillingRatio() + "%"); //$NON-NLS-1$//$NON-NLS-2$
129                                 }
130                         } finally {
131                                 JavaModelManager.VERBOSE = wasVerbose;
132                         }
133                 }
134         }
135         /**
136          * This element is being closed.  Do any necessary cleanup.
137          */
138         protected void closing(Object info) throws JavaModelException {
139         }
140         /**
141          * Returns true if this handle represents the same Java element
142          * as the given handle. By default, two handles represent the same
143          * element if they are identical or if they represent the same type
144          * of element, have equal names, parents, and occurrence counts.
145          *
146          * <p>If a subclass has other requirements for equality, this method
147          * must be overridden.
148          *
149          * @see Object#equals
150          */
151         public boolean equals(Object o) {
152                 
153                 if (this == o) return true;
154         
155                 // Java model parent is null
156                 if (fParent == null) return super.equals(o);
157         
158                 if (o instanceof JavaElement) {
159                         JavaElement other = (JavaElement) o;
160                         if (fLEType != other.fLEType) return false;
161                         
162                         return fName.equals(other.fName) &&
163                                         fParent.equals(other.fParent) &&
164                                         fOccurrenceCount == other.fOccurrenceCount;
165                 }
166                 return false;
167         }
168         /**
169          * Returns true if this <code>JavaElement</code> is equivalent to the given
170          * <code>IDOMNode</code>.
171          */
172         protected boolean equalsDOMNode(IDOMNode node) throws JavaModelException {
173                 return false;
174         }
175         /**
176          * @see IJavaElement
177          */
178         public boolean exists() {
179                 
180                 try {
181                         getElementInfo();
182                         return true;
183                 } catch (JavaModelException e) {
184                 }
185                 return false;
186         }
187         
188         /**
189          * Returns the <code>IDOMNode</code> that corresponds to this <code>JavaElement</code>
190          * or <code>null</code> if there is no corresponding node.
191          */
192         public IDOMNode findNode(IDOMCompilationUnit dom) {
193                 int type = getElementType();
194                 if (type == IJavaElement.COMPILATION_UNIT || 
195                         type == IJavaElement.FIELD || 
196                         type == IJavaElement.IMPORT_DECLARATION || 
197                         type == IJavaElement.INITIALIZER || 
198                         type == IJavaElement.METHOD || 
199                         type == IJavaElement.PACKAGE_DECLARATION || 
200                         type == IJavaElement.TYPE) {
201                         ArrayList path = new ArrayList();
202                         IJavaElement element = this;
203                         while (element != null && element.getElementType() != IJavaElement.COMPILATION_UNIT) {
204                                 if (element.getElementType() != IJavaElement.IMPORT_CONTAINER) {
205                                         // the DOM does not have import containers, so skip them
206                                         path.add(0, element);
207                                 }
208                                 element = element.getParent();
209                         }
210                         if (path.size() == 0) {
211                                 try {
212                                         if (equalsDOMNode(dom)) {
213                                                 return dom;
214                                         } else {
215                                                 return null;
216                                         }
217                                 } catch(JavaModelException e) {
218                                         return null;
219                                 }
220                         }
221                         return ((JavaElement) path.get(0)).followPath(path, 0, dom.getFirstChild());
222                 } else {
223                         return null;
224                 }
225         }
226         /**
227          */
228         protected IDOMNode followPath(ArrayList path, int position, IDOMNode node) {
229         
230                 try {
231                         if (equalsDOMNode(node)) {
232                                 if (position == (path.size() - 1)) {
233                                         return node;
234                                 } else {
235                                         if (node.getFirstChild() != null) {
236                                                 position++;
237                                                 return ((JavaElement)path.get(position)).followPath(path, position, node.getFirstChild());
238                                         } else {
239                                                 return null;
240                                         }
241                                 }
242                         } else if (node.getNextNode() != null) {
243                                 return followPath(path, position, node.getNextNode());
244                         } else {
245                                 return null;
246                         }
247                 } catch (JavaModelException e) {
248                         return null;
249                 }
250         
251         }
252         /**
253          * @see IJavaElement
254          */
255         public IJavaElement getAncestor(int ancestorType) {
256                 
257                 IJavaElement element = this;
258                 while (element != null) {
259                         if (element.getElementType() == ancestorType)  return element;
260                         element= element.getParent();
261                 }
262                 return null;                            
263         }
264         /**
265          * @see IParent 
266          */
267         public IJavaElement[] getChildren() throws JavaModelException {
268                 return ((JavaElementInfo)getElementInfo()).getChildren();
269         }
270         /**
271          * Returns a collection of (immediate) children of this node of the
272          * specified type.
273          *
274          * @param type - one of constants defined by IJavaLanguageElementTypes
275          */
276         public ArrayList getChildrenOfType(int type) throws JavaModelException {
277                 IJavaElement[] children = getChildren();
278                 int size = children.length;
279                 ArrayList list = new ArrayList(size);
280                 for (int i = 0; i < size; ++i) {
281                         JavaElement elt = (JavaElement)children[i];
282                         if (elt.getElementType() == type) {
283                                 list.add(elt);
284                         }
285                 }
286                 return list;
287         }
288         /**
289          * @see IMember
290          */
291 //      public IClassFile getClassFile() {
292 //              return null;
293 //      }
294         /**
295          * @see IMember
296          */
297         public ICompilationUnit getCompilationUnit() {
298                 return null;
299         }
300         /**
301          * Returns the info for this handle.  
302          * If this element is not already open, it and all of its parents are opened.
303          * Does not return null.
304          * NOTE: BinaryType infos are NJOT rooted under JavaElementInfo.
305          * @exception JavaModelException if the element is not present or not accessible
306          */
307         public Object getElementInfo() throws JavaModelException {
308                 // workaround to ensure parent project resolved classpath is available to avoid triggering initializers
309                 // while the JavaModelManager lock is acquired (can cause deadlocks in clients)
310                 IJavaProject project = getJavaProject();
311                 if (project != null && !project.isOpen()) {
312                         // TODO: need to revisit, since deadlock could still occur if perProjectInfo is removed concurrent before entering the lock
313                         try {
314                                 project.getResolvedClasspath(true); // trigger all possible container/variable initialization outside the model lock
315                         } catch (JavaModelException e) {
316                                 // project is not accessible or is not a java project
317                         }
318                 }
319
320                 // element info creation is done inside a lock on the JavaModelManager
321                 JavaModelManager manager;
322                 synchronized(manager = JavaModelManager.getJavaModelManager()){
323                         Object info = manager.getInfo(this);
324                         if (info == null) {
325                                 openHierarchy();
326                                 info= manager.getInfo(this);
327                                 if (info == null) {
328                                         throw newNotPresentException();
329                                 }
330                         }
331                         return info;
332                 }
333         }
334         /**
335          * @see IAdaptable
336          */
337         public String getElementName() {
338                 return fName;
339         }
340         /**
341          * @see IJavaElement
342          */
343         public int getElementType() {
344                 return fLEType;
345         }
346         /**
347          * @see IJavaElement
348          */
349         public String getHandleIdentifier() {
350                 return getHandleMemento();
351         }
352         /**
353          * @see JavaElement#getHandleMemento()
354          */
355         public String getHandleMemento(){
356                 StringBuffer buff= new StringBuffer(((JavaElement)getParent()).getHandleMemento());
357                 buff.append(getHandleMementoDelimiter());
358                 buff.append(getElementName());
359                 return buff.toString();
360         }
361         /**
362          * Returns the <code>char</code> that marks the start of this handles
363          * contribution to a memento.
364          */
365         protected abstract char getHandleMementoDelimiter();
366         /**
367          * @see IJavaElement
368          */
369         public IJavaModel getJavaModel() {
370                 IJavaElement current = this;
371                 do {
372                         if (current instanceof IJavaModel) return (IJavaModel) current;
373                 } while ((current = current.getParent()) != null);
374                 return null;
375         }
376
377         /**
378          * @see IJavaElement
379          */
380         public IJavaProject getJavaProject() {
381                 IJavaElement current = this;
382                 do {
383                         if (current instanceof IJavaProject) return (IJavaProject) current;
384                 } while ((current = current.getParent()) != null);
385                 return null;
386         }
387         /**
388          * Returns the occurrence count of the handle.
389          */
390         protected int getOccurrenceCount() {
391                 return fOccurrenceCount;
392         }
393         /*
394          * @see IJavaElement
395          */
396         public IOpenable getOpenable() {
397                 return this.getOpenableParent();        
398         }
399         /**
400          * Return the first instance of IOpenable in the parent
401          * hierarchy of this element.
402          *
403          * <p>Subclasses that are not IOpenable's must override this method.
404          */
405         public IOpenable getOpenableParent() {
406                 
407                 return (IOpenable)fParent;
408         }
409         /**
410          * @see IJavaElement
411          */
412         public IJavaElement getParent() {
413                 return fParent;
414         }
415         
416         /**
417          * Returns the element that is located at the given source position
418          * in this element.  This is a helper method for <code>ICompilationUnit#getElementAt</code>,
419          * and only works on compilation units and types. The position given is
420          * known to be within this element's source range already, and if no finer
421          * grained element is found at the position, this element is returned.
422          */
423         protected IJavaElement getSourceElementAt(int position) throws JavaModelException {
424                 if (this instanceof ISourceReference) {
425                         IJavaElement[] children = getChildren();
426                         int i;
427                         for (i = 0; i < children.length; i++) {
428                                 IJavaElement aChild = children[i];
429                                 if (aChild instanceof SourceRefElement) {
430                                         SourceRefElement child = (SourceRefElement) children[i];
431                                         ISourceRange range = child.getSourceRange();
432                                         if (position < range.getOffset() + range.getLength() && position >= range.getOffset()) {
433                                                 if (child instanceof IParent) {
434                                                         return child.getSourceElementAt(position);
435                                                 } else {
436                                                         return child;
437                                                 }
438                                         }
439                                 }
440                         }
441                 } else {
442                         // should not happen
443                         Assert.isTrue(false);
444                 }
445                 return this;
446         }
447         /**
448          * Returns the SourceMapper facility for this element, or
449          * <code>null</code> if this element does not have a
450          * SourceMapper.
451          */
452 //      public SourceMapper getSourceMapper() {
453 //              return ((JavaElement)getParent()).getSourceMapper();
454 //      }
455
456         /**
457          * Returns the hash code for this Java element. By default,
458          * the hash code for an element is a combination of its name
459          * and parent's hash code. Elements with other requirements must
460          * override this method.
461          */
462         public int hashCode() {
463                 if (fParent == null) return super.hashCode();
464                 return Util.combineHashCodes(fName.hashCode(), fParent.hashCode());
465         }
466         /**
467          * Returns true if this element is an ancestor of the given element,
468          * otherwise false.
469          */
470         protected boolean isAncestorOf(IJavaElement e) {
471                 IJavaElement parent= e.getParent();
472                 while (parent != null && !parent.equals(this)) {
473                         parent= parent.getParent();
474                 }
475                 return parent != null;
476         }
477         
478         /**
479          * @see IJavaElement
480          */
481         public boolean isReadOnly() {
482                 return false;
483         }
484         /**
485          * @see IJavaElement
486          */
487         public boolean isStructureKnown() throws JavaModelException {
488                 return ((JavaElementInfo)getElementInfo()).isStructureKnown();
489         }
490         /**
491          * Creates and returns and not present exception for this element.
492          */
493         protected JavaModelException newNotPresentException() {
494                 return new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.ELEMENT_DOES_NOT_EXIST, this));
495         }
496         /**
497          * Opens this element and all parents that are not already open.
498          *
499          * @exception JavaModelException this element is not present or accessible
500          */
501         protected void openHierarchy() throws JavaModelException {
502                 if (this instanceof IOpenable) {
503                         ((Openable) this).openWhenClosed(null);
504                 } else {
505                         Openable openableParent = (Openable)getOpenableParent();
506                         if (openableParent != null) {
507                                 JavaElementInfo openableParentInfo = (JavaElementInfo) JavaModelManager.getJavaModelManager().getInfo((IJavaElement) openableParent);
508                                 if (openableParentInfo == null) {
509                                         openableParent.openWhenClosed(null);
510                                 } else {
511                                         throw newNotPresentException();
512                                 }
513                         }
514                 }
515         }
516         /**
517          * This element has just been opened.  Do any necessary setup.
518          */
519         protected void opening(Object info) {
520         }
521         /**
522          */
523         public String readableName() {
524                 return this.getElementName();
525         }
526         /**
527          * Removes all cached info from the Java Model, including all children,
528          * but does not close this element.
529          */
530         protected void removeInfo() {
531                 Object info = JavaModelManager.getJavaModelManager().peekAtInfo(this);
532                 if (info != null) {
533                         if (this instanceof IParent) {
534                                 IJavaElement[] children = ((JavaElementInfo)info).getChildren();
535                                 for (int i = 0, size = children.length; i < size; ++i) {
536                                         JavaElement child = (JavaElement) children[i];
537                                         child.removeInfo();
538                                 }
539                         }
540                         JavaModelManager.getJavaModelManager().removeInfo(this);
541                 }
542         }
543         /**
544          * Returns a copy of this element rooted at the given project.
545          */
546         public abstract IJavaElement rootedAt(IJavaProject project);
547         /**
548          * Runs a Java Model Operation
549          */
550         public static void runOperation(JavaModelOperation operation, IProgressMonitor monitor) throws JavaModelException {
551                 try {
552                         if (operation.isReadOnly() || ResourcesPlugin.getWorkspace().isTreeLocked()) {
553                                 operation.run(monitor);
554                         } else {
555                                 // use IWorkspace.run(...) to ensure that a build will be done in autobuild mode
556                                 ResourcesPlugin.getWorkspace().run(operation, monitor);
557                         }
558                 } catch (CoreException ce) {
559                         if (ce instanceof JavaModelException) {
560                                 throw (JavaModelException)ce;
561                         } else {
562                                 if (ce.getStatus().getCode() == IResourceStatus.OPERATION_FAILED) {
563                                         Throwable e= ce.getStatus().getException();
564                                         if (e instanceof JavaModelException) {
565                                                 throw (JavaModelException) e;
566                                         }
567                                 }
568                                 throw new JavaModelException(ce);
569                         }
570                 }
571         }
572         /**
573          * Sets the occurrence count of the handle.
574          */
575         protected void setOccurrenceCount(int count) {
576                 fOccurrenceCount = count;
577         }
578         protected String tabString(int tab) {
579                 StringBuffer buffer = new StringBuffer();
580                 for (int i = tab; i > 0; i--)
581                         buffer.append("  "); //$NON-NLS-1$
582                 return buffer.toString();
583         }
584         /**
585          * Debugging purposes
586          */
587         public String toDebugString() {
588                 StringBuffer buffer = new StringBuffer();
589                 this.toStringInfo(0, buffer, NO_INFO);
590                 return buffer.toString();
591         }
592         /**
593          *  Debugging purposes
594          */
595         public String toString() {
596                 StringBuffer buffer = new StringBuffer();
597                 toString(0, buffer);
598                 return buffer.toString();
599         }
600         /**
601          *  Debugging purposes
602          */
603         protected void toString(int tab, StringBuffer buffer) {
604         //      Object info = this.toStringInfo(tab, buffer);
605                 Object info = null;
606                 if (tab == 0) {
607                         this.toStringAncestors(buffer);
608                 }
609                 this.toStringChildren(tab, buffer, info);
610         }
611         /**
612          *  Debugging purposes
613          */
614         public String toStringWithAncestors() {
615                 StringBuffer buffer = new StringBuffer();
616                 this.toStringInfo(0, buffer, NO_INFO);
617                 this.toStringAncestors(buffer);
618                 return buffer.toString();
619         }
620         /**
621          *  Debugging purposes
622          */
623         protected void toStringAncestors(StringBuffer buffer) {
624                 JavaElement parent = (JavaElement)this.getParent();
625                 if (parent != null && parent.getParent() != null) {
626                         buffer.append(" [in "); //$NON-NLS-1$
627                         parent.toStringInfo(0, buffer, NO_INFO);
628                         parent.toStringAncestors(buffer);
629                         buffer.append("]"); //$NON-NLS-1$
630                 }
631         }
632         /**
633          *  Debugging purposes
634          */
635         protected void toStringChildren(int tab, StringBuffer buffer, Object info) {
636                 if (info == null || !(info instanceof JavaElementInfo)) return;
637                 IJavaElement[] children = ((JavaElementInfo)info).getChildren();
638                 for (int i = 0; i < children.length; i++) {
639                         buffer.append("\n"); //$NON-NLS-1$
640                         ((JavaElement)children[i]).toString(tab + 1, buffer);
641                 }
642         }
643         /**
644          *  Debugging purposes
645          */
646 //      public Object toStringInfo(int tab, StringBuffer buffer) {
647 //              Object info = JavaModelManager.getJavaModelManager().peekAtInfo(this);
648 //              this.toStringInfo(tab, buffer, info);
649 //              return info;
650 //      }
651         /**
652          *  Debugging purposes
653          */
654         protected void toStringInfo(int tab, StringBuffer buffer, Object info) {
655                 buffer.append(this.tabString(tab));
656                 buffer.append(getElementName());
657                 if (info == null) {
658                         buffer.append(" (not open)"); //$NON-NLS-1$
659                 }
660         }
661 }