1 /*******************************************************************************
2 * Copyright (c) 2000, 2011 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package org.eclipse.jdt.internal.corext.util;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.Collections;
17 import java.util.HashMap;
18 import java.util.Iterator;
19 import java.util.List;
22 import org.w3c.dom.Element;
24 import org.eclipse.core.filesystem.EFS;
25 import org.eclipse.core.filesystem.IFileInfo;
27 import org.eclipse.core.runtime.CoreException;
28 import org.eclipse.core.runtime.IProgressMonitor;
29 import org.eclipse.core.runtime.IStatus;
30 import org.eclipse.core.runtime.OperationCanceledException;
31 import org.eclipse.core.runtime.Status;
32 import org.eclipse.core.runtime.jobs.Job;
34 import org.eclipse.core.resources.IResource;
36 import org.eclipse.core.filebuffers.FileBuffers;
37 import org.eclipse.core.filebuffers.ITextFileBuffer;
38 import org.eclipse.core.filebuffers.ITextFileBufferManager;
39 import org.eclipse.core.filebuffers.LocationKind;
41 import org.eclipse.jdt.core.ElementChangedEvent;
42 import org.eclipse.jdt.core.ICompilationUnit;
43 import org.eclipse.jdt.core.IElementChangedListener;
44 import org.eclipse.jdt.core.IJavaElement;
45 import org.eclipse.jdt.core.IJavaElementDelta;
46 import org.eclipse.jdt.core.IPackageFragmentRoot;
47 import org.eclipse.jdt.core.IType;
48 import org.eclipse.jdt.core.JavaCore;
49 import org.eclipse.jdt.core.JavaModelException;
50 import org.eclipse.jdt.core.search.SearchEngine;
51 import org.eclipse.jdt.core.search.TypeNameMatch;
53 import org.eclipse.jdt.internal.corext.CorextMessages;
56 * History for the open type dialog. Object and keys are both {@link TypeNameMatch}s.
58 public class OpenTypeHistory extends History {
60 private static class TypeHistoryDeltaListener implements IElementChangedListener {
61 public void elementChanged(ElementChangedEvent event) {
62 if (processDelta(event.getDelta())) {
63 OpenTypeHistory.getInstance().markAsInconsistent();
68 * Computes whether the history needs a consistency check or not.
70 * @param delta the Java element delta
72 * @return <code>true</code> if consistency must be checked
73 * <code>false</code> otherwise.
75 private boolean processDelta(IJavaElementDelta delta) {
76 IJavaElement elem= delta.getElement();
78 boolean isChanged= delta.getKind() == IJavaElementDelta.CHANGED;
79 boolean isRemoved= delta.getKind() == IJavaElementDelta.REMOVED;
81 switch (elem.getElementType()) {
82 case IJavaElement.JAVA_PROJECT:
83 if (isRemoved || (isChanged &&
84 (delta.getFlags() & IJavaElementDelta.F_CLOSED) != 0)) {
87 return processChildrenDelta(delta);
88 case IJavaElement.PACKAGE_FRAGMENT_ROOT:
89 if (isRemoved || (isChanged && (
90 (delta.getFlags() & IJavaElementDelta.F_ARCHIVE_CONTENT_CHANGED) != 0 ||
91 (delta.getFlags() & IJavaElementDelta.F_REMOVED_FROM_CLASSPATH) != 0))) {
94 return processChildrenDelta(delta);
95 case IJavaElement.TYPE:
96 if (isChanged && (delta.getFlags() & IJavaElementDelta.F_MODIFIERS) != 0) {
102 return processChildrenDelta(delta);
103 case IJavaElement.JAVA_MODEL:
104 case IJavaElement.PACKAGE_FRAGMENT:
105 case IJavaElement.CLASS_FILE:
109 return processChildrenDelta(delta);
110 case IJavaElement.COMPILATION_UNIT:
111 // Not the primary compilation unit. Ignore it
112 if (!JavaModelUtil.isPrimary((ICompilationUnit) elem)) {
116 if (isRemoved || (isChanged && isUnknownStructuralChange(delta.getFlags()))) {
119 return processChildrenDelta(delta);
121 // fields, methods, imports ect
126 private boolean isUnknownStructuralChange(int flags) {
127 if ((flags & IJavaElementDelta.F_CONTENT) == 0)
129 return (flags & IJavaElementDelta.F_FINE_GRAINED) == 0;
133 private boolean isPossibleStructuralChange(int flags) {
134 return (flags & (IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_FINE_GRAINED)) == IJavaElementDelta.F_CONTENT;
138 private boolean processChildrenDelta(IJavaElementDelta delta) {
139 IJavaElementDelta[] children= delta.getAffectedChildren();
140 for (int i= 0; i < children.length; i++) {
141 if (processDelta(children[i])) {
149 private static class UpdateJob extends Job {
150 public static final String FAMILY= UpdateJob.class.getName();
152 super(CorextMessages.TypeInfoHistory_consistency_check);
155 protected IStatus run(IProgressMonitor monitor) {
156 OpenTypeHistory history= OpenTypeHistory.getInstance();
157 history.internalCheckConsistency(monitor);
158 return Status.OK_STATUS;
161 public boolean belongsTo(Object family) {
162 return FAMILY.equals(family);
166 // Needs to be volatile since accesses aren't synchronized.
167 private volatile boolean fNeedsConsistencyCheck;
168 // Map of cached time stamps
169 private Map<TypeNameMatch, Long> fTimestampMapping;
171 private final IElementChangedListener fDeltaListener;
172 private final UpdateJob fUpdateJob;
174 private static final String FILENAME= "OpenTypeHistory.xml"; //$NON-NLS-1$
175 private static final String NODE_ROOT= "typeInfoHistroy"; //$NON-NLS-1$
176 private static final String NODE_TYPE_INFO= "typeInfo"; //$NON-NLS-1$
177 private static final String NODE_HANDLE= "handle"; //$NON-NLS-1$
178 private static final String NODE_MODIFIERS= "modifiers"; //$NON-NLS-1$
179 private static final String NODE_TIMESTAMP= "timestamp"; //$NON-NLS-1$
181 private static OpenTypeHistory fgInstance;
183 public static synchronized OpenTypeHistory getInstance() {
184 if (fgInstance == null)
185 fgInstance= new OpenTypeHistory();
189 public static synchronized void shutdown() {
190 if (fgInstance == null)
192 fgInstance.doShutdown();
195 private OpenTypeHistory() {
196 super(FILENAME, NODE_ROOT, NODE_TYPE_INFO);
197 fTimestampMapping= new HashMap<TypeNameMatch, Long>();
198 fNeedsConsistencyCheck= true;
200 fDeltaListener= new TypeHistoryDeltaListener();
201 JavaCore.addElementChangedListener(fDeltaListener);
202 fUpdateJob= new UpdateJob();
203 // It is not necessary anymore that the update job has a rule since
204 // markAsInconsistent isn't synchronized anymore. See bugs
205 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=128399 and
206 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=135278
208 fUpdateJob.setPriority(Job.SHORT);
211 public void markAsInconsistent() {
212 fNeedsConsistencyCheck= true;
213 // cancel the old job. If no job is running this is a NOOP.
215 fUpdateJob.schedule();
218 public boolean needConsistencyCheck() {
219 return fNeedsConsistencyCheck;
222 public void checkConsistency(IProgressMonitor monitor) throws OperationCanceledException {
223 if (!fNeedsConsistencyCheck)
225 if (fUpdateJob.getState() == Job.RUNNING) {
227 Job.getJobManager().join(UpdateJob.FAMILY, monitor);
228 } catch (OperationCanceledException e) {
229 // Ignore and do the consistency check without
230 // waiting for the update job.
231 } catch (InterruptedException e) {
232 // Ignore and do the consistency check without
233 // waiting for the update job.
236 if (!fNeedsConsistencyCheck)
238 internalCheckConsistency(monitor);
241 public synchronized boolean contains(TypeNameMatch type) {
242 return super.contains(type);
245 public synchronized void accessed(TypeNameMatch info) {
246 // Fetching the timestamp might not be cheap (remote file system
247 // external Jars. So check if we alreay have one.
248 if (!fTimestampMapping.containsKey(info)) {
249 fTimestampMapping.put(info, new Long(getContainerTimestamp(info)));
251 super.accessed(info);
254 public synchronized TypeNameMatch remove(TypeNameMatch info) {
255 fTimestampMapping.remove(info);
256 return (TypeNameMatch)super.remove(info);
259 public synchronized void replace(TypeNameMatch old, TypeNameMatch newMatch) {
260 fTimestampMapping.remove(old);
261 fTimestampMapping.put(newMatch, new Long(getContainerTimestamp(newMatch)));
263 super.accessed(newMatch);
266 public synchronized TypeNameMatch[] getTypeInfos() {
267 Collection<Object> values= getValues();
268 int size= values.size();
269 TypeNameMatch[] result= new TypeNameMatch[size];
271 for (Iterator<Object> iter= values.iterator(); iter.hasNext();) {
272 result[i]= (TypeNameMatch)iter.next();
278 public synchronized TypeNameMatch[] getFilteredTypeInfos(TypeInfoFilter filter) {
279 Collection<Object> values= getValues();
280 List<TypeNameMatch> result= new ArrayList<TypeNameMatch>();
281 for (Iterator<Object> iter= values.iterator(); iter.hasNext();) {
282 TypeNameMatch type= (TypeNameMatch)iter.next();
283 if ((filter == null || filter.matchesHistoryElement(type)) && !TypeFilter.isFiltered(type.getFullyQualifiedName()))
286 Collections.reverse(result);
287 return result.toArray(new TypeNameMatch[result.size()]);
292 protected Object getKey(Object object) {
296 private synchronized void internalCheckConsistency(IProgressMonitor monitor) throws OperationCanceledException {
297 // Setting fNeedsConsistencyCheck is necessary here since
298 // markAsInconsistent isn't synchronized.
299 fNeedsConsistencyCheck= true;
300 List<Object> typesToCheck= new ArrayList<Object>(getKeys());
301 monitor.beginTask(CorextMessages.TypeInfoHistory_consistency_check, typesToCheck.size());
302 monitor.setTaskName(CorextMessages.TypeInfoHistory_consistency_check);
303 for (Iterator<Object> iter= typesToCheck.iterator(); iter.hasNext();) {
304 TypeNameMatch type= (TypeNameMatch)iter.next();
305 long currentTimestamp= getContainerTimestamp(type);
306 Long lastTested= fTimestampMapping.get(type);
307 if (lastTested != null && currentTimestamp != IResource.NULL_STAMP && currentTimestamp == lastTested.longValue() && !isContainerDirty(type))
310 IType jType= type.getType();
311 if (jType == null || !jType.exists()) {
314 // copy over the modifiers since they may have changed
315 int modifiers= jType.getFlags();
316 if (modifiers != type.getModifiers()) {
317 replace(type, SearchEngine.createTypeNameMatch(jType, modifiers));
319 fTimestampMapping.put(type, new Long(currentTimestamp));
322 } catch (JavaModelException e) {
325 if (monitor.isCanceled())
326 throw new OperationCanceledException();
330 fNeedsConsistencyCheck= false;
333 private long getContainerTimestamp(TypeNameMatch match) {
335 IType type= match.getType();
336 IResource resource= type.getResource();
337 if (resource != null) {
338 URI location= resource.getLocationURI();
339 if (location != null) {
340 IFileInfo info= EFS.getStore(location).fetchInfo();
342 // The element could be removed from the build path. So check
343 // if the Java element still exists.
344 IJavaElement element= JavaCore.create(resource);
345 if (element != null && element.exists())
346 return info.getLastModified();
349 } else { // external JAR
350 IPackageFragmentRoot root= match.getPackageFragmentRoot();
352 IFileInfo info= EFS.getLocalFileSystem().getStore(root.getPath()).fetchInfo();
354 return info.getLastModified();
358 } catch (CoreException e) {
361 return IResource.NULL_STAMP;
365 public boolean isContainerDirty(TypeNameMatch match) {
366 ICompilationUnit cu= match.getType().getCompilationUnit();
370 IResource resource= cu.getResource();
371 ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager();
372 ITextFileBuffer textFileBuffer= manager.getTextFileBuffer(resource.getFullPath(), LocationKind.IFILE);
373 if (textFileBuffer != null) {
374 return textFileBuffer.isDirty();
380 private void doShutdown() {
381 JavaCore.removeElementChangedListener(fDeltaListener);
386 protected Object createFromElement(Element type) {
387 String handle= type.getAttribute(NODE_HANDLE);
391 IJavaElement element= JavaCore.create(handle);
392 if (!(element instanceof IType))
397 modifiers= Integer.parseInt(type.getAttribute(NODE_MODIFIERS));
398 } catch (NumberFormatException e) {
401 TypeNameMatch info= SearchEngine.createTypeNameMatch((IType) element, modifiers);
402 long timestamp= IResource.NULL_STAMP;
403 String timestampValue= type.getAttribute(NODE_TIMESTAMP);
404 if (timestampValue != null && timestampValue.length() > 0) {
406 timestamp= Long.parseLong(timestampValue);
407 } catch (NumberFormatException e) {
411 if (timestamp != IResource.NULL_STAMP) {
412 fTimestampMapping.put(info, new Long(timestamp));
418 protected void setAttributes(Object object, Element typeElement) {
419 TypeNameMatch type= (TypeNameMatch) object;
420 String handleId= type.getType().getHandleIdentifier();
421 typeElement.setAttribute(NODE_HANDLE, handleId);
422 typeElement.setAttribute(NODE_MODIFIERS, Integer.toString(type.getModifiers()));
423 Long timestamp= fTimestampMapping.get(type);
424 if (timestamp == null) {
425 typeElement.setAttribute(NODE_TIMESTAMP, Long.toString(IResource.NULL_STAMP));
427 typeElement.setAttribute(NODE_TIMESTAMP, timestamp.toString());