blob: 9816a3ddc5267fabd4c18d770004adf4dddccfa0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2015 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.corext.util;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Element;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.filebuffers.LocationKind;
import org.eclipse.jdt.core.ElementChangedEvent;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IElementChangedListener;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaElementDelta;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.TypeNameMatch;
import org.eclipse.jdt.internal.corext.CorextMessages;
/**
* History for the open type dialog. Object and keys are both {@link TypeNameMatch}s.
*/
public class OpenTypeHistory extends History<TypeNameMatch, TypeNameMatch> {
private static class TypeHistoryDeltaListener implements IElementChangedListener {
@Override
public void elementChanged(ElementChangedEvent event) {
if (processDelta(event.getDelta())) {
OpenTypeHistory.getInstance().markAsInconsistent();
}
}
/**
* Computes whether the history needs a consistency check or not.
*
* @param delta the Java element delta
*
* @return <code>true</code> if consistency must be checked
* <code>false</code> otherwise.
*/
private boolean processDelta(IJavaElementDelta delta) {
IJavaElement elem= delta.getElement();
boolean isChanged= delta.getKind() == IJavaElementDelta.CHANGED;
boolean isRemoved= delta.getKind() == IJavaElementDelta.REMOVED;
switch (elem.getElementType()) {
case IJavaElement.JAVA_PROJECT:
if (isRemoved || (isChanged &&
(delta.getFlags() & IJavaElementDelta.F_CLOSED) != 0)) {
return true;
}
return processChildrenDelta(delta);
case IJavaElement.PACKAGE_FRAGMENT_ROOT:
if (isRemoved || (isChanged && (
(delta.getFlags() & IJavaElementDelta.F_ARCHIVE_CONTENT_CHANGED) != 0 ||
(delta.getFlags() & IJavaElementDelta.F_REMOVED_FROM_CLASSPATH) != 0))) {
return true;
}
return processChildrenDelta(delta);
case IJavaElement.TYPE:
if (isChanged && (delta.getFlags() & IJavaElementDelta.F_MODIFIERS) != 0) {
return true;
}
if (isRemoved) {
return true;
}
return processChildrenDelta(delta);
case IJavaElement.JAVA_MODEL:
case IJavaElement.PACKAGE_FRAGMENT:
case IJavaElement.CLASS_FILE:
if (isRemoved) {
return true;
}
return processChildrenDelta(delta);
case IJavaElement.COMPILATION_UNIT:
// Not the primary compilation unit. Ignore it
if (!JavaModelUtil.isPrimary((ICompilationUnit) elem)) {
return false;
}
if (isRemoved || (isChanged && isUnknownStructuralChange(delta.getFlags()))) {
return true;
}
return processChildrenDelta(delta);
default:
// fields, methods, imports ect
return false;
}
}
private boolean isUnknownStructuralChange(int flags) {
if ((flags & IJavaElementDelta.F_CONTENT) == 0)
return false;
return (flags & IJavaElementDelta.F_FINE_GRAINED) == 0;
}
/*
private boolean isPossibleStructuralChange(int flags) {
return (flags & (IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_FINE_GRAINED)) == IJavaElementDelta.F_CONTENT;
}
*/
private boolean processChildrenDelta(IJavaElementDelta delta) {
for (IJavaElementDelta child : delta.getAffectedChildren()) {
if (processDelta(child)) {
return true;
}
}
return false;
}
}
private static class UpdateJob extends Job {
public static final String FAMILY= UpdateJob.class.getName();
public UpdateJob() {
super(CorextMessages.TypeInfoHistory_consistency_check);
}
@Override
protected IStatus run(IProgressMonitor monitor) {
OpenTypeHistory history= OpenTypeHistory.getInstance();
history.internalCheckConsistency(monitor);
return Status.OK_STATUS;
}
@Override
public boolean belongsTo(Object family) {
return FAMILY.equals(family);
}
}
// Needs to be volatile since accesses aren't synchronized.
private volatile boolean fNeedsConsistencyCheck;
// Map of cached time stamps
private Map<TypeNameMatch, Long> fTimestampMapping;
private final IElementChangedListener fDeltaListener;
private final UpdateJob fUpdateJob;
private static final String FILENAME= "OpenTypeHistory.xml"; //$NON-NLS-1$
private static final String NODE_ROOT= "typeInfoHistroy"; //$NON-NLS-1$
private static final String NODE_TYPE_INFO= "typeInfo"; //$NON-NLS-1$
private static final String NODE_HANDLE= "handle"; //$NON-NLS-1$
private static final String NODE_MODIFIERS= "modifiers"; //$NON-NLS-1$
private static final String NODE_TIMESTAMP= "timestamp"; //$NON-NLS-1$
private static OpenTypeHistory fgInstance;
public static synchronized OpenTypeHistory getInstance() {
if (fgInstance == null)
fgInstance= new OpenTypeHistory();
return fgInstance;
}
public static synchronized void shutdown() {
if (fgInstance == null)
return;
fgInstance.doShutdown();
}
private OpenTypeHistory() {
super(FILENAME, NODE_ROOT, NODE_TYPE_INFO);
fTimestampMapping= new HashMap<>();
fNeedsConsistencyCheck= true;
load();
fDeltaListener= new TypeHistoryDeltaListener();
JavaCore.addElementChangedListener(fDeltaListener);
fUpdateJob= new UpdateJob();
// It is not necessary anymore that the update job has a rule since
// markAsInconsistent isn't synchronized anymore. See bugs
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=128399 and
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=135278
// for details.
fUpdateJob.setPriority(Job.SHORT);
}
public void markAsInconsistent() {
fNeedsConsistencyCheck= true;
// cancel the old job. If no job is running this is a NOOP.
fUpdateJob.cancel();
fUpdateJob.schedule();
}
public boolean needConsistencyCheck() {
return fNeedsConsistencyCheck;
}
public void checkConsistency(IProgressMonitor monitor) throws OperationCanceledException {
if (!fNeedsConsistencyCheck)
return;
if (fUpdateJob.getState() == Job.RUNNING) {
try {
Job.getJobManager().join(UpdateJob.FAMILY, monitor);
} catch (OperationCanceledException | InterruptedException e) {
// Ignore and do the consistency check without
// waiting for the update job.
}
}
if (!fNeedsConsistencyCheck)
return;
internalCheckConsistency(monitor);
}
@Override
public synchronized boolean contains(TypeNameMatch type) {
return super.contains(type);
}
@Override
public synchronized void accessed(TypeNameMatch info) {
// Fetching the timestamp might not be cheap (remote file system
// external Jars. So check if we alreay have one.
if (!fTimestampMapping.containsKey(info)) {
fTimestampMapping.put(info, getContainerTimestamp(info));
}
super.accessed(info);
}
@Override
public synchronized TypeNameMatch remove(TypeNameMatch info) {
fTimestampMapping.remove(info);
return (TypeNameMatch)super.remove(info);
}
public synchronized void replace(TypeNameMatch old, TypeNameMatch newMatch) {
fTimestampMapping.remove(old);
fTimestampMapping.put(newMatch, getContainerTimestamp(newMatch));
super.remove(old);
super.accessed(newMatch);
}
public synchronized TypeNameMatch[] getTypeInfos() {
Collection<TypeNameMatch> values= getValues();
int size= values.size();
TypeNameMatch[] result= new TypeNameMatch[size];
int i= size - 1;
for (TypeNameMatch typeNameMatch : values) {
result[i]= typeNameMatch;
i--;
}
return result;
}
public synchronized TypeNameMatch[] getFilteredTypeInfos(TypeInfoFilter filter) {
List<TypeNameMatch> result= new ArrayList<>();
for (TypeNameMatch type : getValues()) {
if ((filter == null || filter.matchesHistoryElement(type)) && !TypeFilter.isFiltered(type.getFullyQualifiedName()))
result.add(type);
}
Collections.reverse(result);
return result.toArray(new TypeNameMatch[result.size()]);
}
@Override
protected TypeNameMatch getKey(TypeNameMatch object) {
return object;
}
private synchronized void internalCheckConsistency(IProgressMonitor monitor) throws OperationCanceledException {
// Setting fNeedsConsistencyCheck is necessary here since
// markAsInconsistent isn't synchronized.
fNeedsConsistencyCheck= true;
List<TypeNameMatch> typesToCheck= new ArrayList<>(getKeys());
monitor.beginTask(CorextMessages.TypeInfoHistory_consistency_check, typesToCheck.size());
monitor.setTaskName(CorextMessages.TypeInfoHistory_consistency_check);
for (TypeNameMatch type : typesToCheck) {
long currentTimestamp= getContainerTimestamp(type);
Long lastTested= fTimestampMapping.get(type);
if (lastTested != null && currentTimestamp != IResource.NULL_STAMP && currentTimestamp == lastTested.longValue() && !isContainerDirty(type))
continue;
try {
IType jType= type.getType();
if (jType == null || !jType.exists()) {
remove(type);
} else {
// copy over the modifiers since they may have changed
int modifiers= jType.getFlags();
if (modifiers != type.getModifiers()) {
replace(type, SearchEngine.createTypeNameMatch(jType, modifiers));
} else {
fTimestampMapping.put(type, currentTimestamp);
}
}
} catch (JavaModelException e) {
remove(type);
}
if (monitor.isCanceled())
throw new OperationCanceledException();
monitor.worked(1);
}
monitor.done();
fNeedsConsistencyCheck= false;
}
private long getContainerTimestamp(TypeNameMatch match) {
try {
IType type= match.getType();
IResource resource= type.getResource();
if (resource != null) {
URI location= resource.getLocationURI();
if (location != null) {
IFileInfo info= EFS.getStore(location).fetchInfo();
if (info.exists()) {
// The element could be removed from the build path. So check
// if the Java element still exists.
IJavaElement element= JavaCore.create(resource);
if (element != null && element.exists())
return info.getLastModified();
}
}
} else { // external JAR
IPackageFragmentRoot root= match.getPackageFragmentRoot();
if (root.exists()) {
IFileInfo info= EFS.getLocalFileSystem().getStore(root.getPath()).fetchInfo();
if (info.exists()) {
return info.getLastModified();
}
}
}
} catch (CoreException e) {
// Fall through
}
return IResource.NULL_STAMP;
}
public boolean isContainerDirty(TypeNameMatch match) {
ICompilationUnit cu= match.getType().getCompilationUnit();
if (cu == null) {
return false;
}
IResource resource= cu.getResource();
ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager();
ITextFileBuffer textFileBuffer= manager.getTextFileBuffer(resource.getFullPath(), LocationKind.IFILE);
if (textFileBuffer != null) {
return textFileBuffer.isDirty();
}
return false;
}
private void doShutdown() {
JavaCore.removeElementChangedListener(fDeltaListener);
save();
}
@Override
protected TypeNameMatch createFromElement(Element type) {
String handle= type.getAttribute(NODE_HANDLE);
if (handle == null )
return null;
IJavaElement element= JavaCore.create(handle);
if (!(element instanceof IType))
return null;
int modifiers= 0;
try {
modifiers= Integer.parseInt(type.getAttribute(NODE_MODIFIERS));
} catch (NumberFormatException e) {
// take zero
}
TypeNameMatch info= SearchEngine.createTypeNameMatch((IType) element, modifiers);
long timestamp= IResource.NULL_STAMP;
String timestampValue= type.getAttribute(NODE_TIMESTAMP);
if (timestampValue != null && timestampValue.length() > 0) {
try {
timestamp= Long.parseLong(timestampValue);
} catch (NumberFormatException e) {
// take null stamp
}
}
if (timestamp != IResource.NULL_STAMP) {
fTimestampMapping.put(info, timestamp);
}
return info;
}
@Override
protected void setAttributes(Object object, Element typeElement) {
TypeNameMatch type= (TypeNameMatch) object;
String handleId= type.getType().getHandleIdentifier();
typeElement.setAttribute(NODE_HANDLE, handleId);
typeElement.setAttribute(NODE_MODIFIERS, Integer.toString(type.getModifiers()));
Long timestamp= fTimestampMapping.get(type);
if (timestamp == null) {
typeElement.setAttribute(NODE_TIMESTAMP, Long.toString(IResource.NULL_STAMP));
} else {
typeElement.setAttribute(NODE_TIMESTAMP, timestamp.toString());
}
}
}