blob: 417bb257ac649683129ee5e35a979682311e2aeb [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2018 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
*******************************************************************************/
package org.eclipse.dltk.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.Iterator;
import java.util.List;
import java.util.Map;
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.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.resources.IResource;
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.dltk.core.DLTKCore;
import org.eclipse.dltk.core.ElementChangedEvent;
import org.eclipse.dltk.core.IElementChangedListener;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.IModelElementDelta;
import org.eclipse.dltk.core.IProjectFragment;
import org.eclipse.dltk.core.IShutdownListener;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.IType;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.dltk.core.ScriptModelUtil;
import org.eclipse.dltk.core.search.SearchEngine;
import org.eclipse.dltk.core.search.TypeNameMatch;
import org.eclipse.dltk.ui.DLTKUIPlugin;
import org.eclipse.dltk.ui.IDLTKUILanguageToolkit;
import org.w3c.dom.Element;
/**
* History for the open type dialog. Object and keys are both
* {@link TypeNameMatch}s.
*/
public class OpenTypeHistory extends History implements IShutdownListener {
// private IDLTKUILanguageToolkit fToolkit = null;
TypeFilter fTypeFilter = null;
private static Map<IDLTKUILanguageToolkit, OpenTypeHistory> sToolkitHistory = new HashMap<>();
public static synchronized OpenTypeHistory getInstance(
IDLTKUILanguageToolkit toolkit) {
if (sToolkitHistory.containsKey(toolkit)) {
return sToolkitHistory.get(toolkit);
}
OpenTypeHistory his = new OpenTypeHistory(toolkit);
sToolkitHistory.put(toolkit, his);
return his;
}
private class TypeHistoryDeltaListener implements IElementChangedListener {
@Override
public void elementChanged(ElementChangedEvent event) {
if (processDelta(event.getDelta())) {
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(IModelElementDelta delta) {
IModelElement elem = delta.getElement();
boolean isChanged = delta.getKind() == IModelElementDelta.CHANGED;
boolean isRemoved = delta.getKind() == IModelElementDelta.REMOVED;
switch (elem.getElementType()) {
case IModelElement.SCRIPT_PROJECT:
if (isRemoved || (isChanged && (delta.getFlags()
& IModelElementDelta.F_CLOSED) != 0)) {
return true;
}
return processChildrenDelta(delta);
case IModelElement.PROJECT_FRAGMENT:
if (isRemoved || (isChanged && ((delta.getFlags()
& IModelElementDelta.F_ARCHIVE_CONTENT_CHANGED) != 0
|| (delta.getFlags()
& IModelElementDelta.F_REMOVED_FROM_BUILDPATH) != 0))) {
return true;
}
return processChildrenDelta(delta);
case IModelElement.TYPE:
if (isChanged && (delta.getFlags()
& IModelElementDelta.F_MODIFIERS) != 0) {
return true;
}
// type children can be inner classes: fall through
case IModelElement.SCRIPT_MODEL:
case IModelElement.SCRIPT_FOLDER:
if (isRemoved) {
return true;
}
return processChildrenDelta(delta);
case IModelElement.SOURCE_MODULE:
// Not the primary compilation unit. Ignore it
if (!ScriptModelUtil.isPrimary((ISourceModule) 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 & IModelElementDelta.F_CONTENT) == 0)
return false;
return (flags & IModelElementDelta.F_FINE_GRAINED) == 0;
}
/*
* private boolean isPossibleStructuralChange(int flags) { return (flags
* & (IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_FINE_GRAINED))
* == IJavaElementDelta.F_CONTENT; }
*/
private boolean processChildrenDelta(IModelElementDelta delta) {
IModelElementDelta[] children = delta.getAffectedChildren();
for (int i = 0; i < children.length; i++) {
if (processDelta(children[i])) {
return true;
}
}
return false;
}
}
private static final String FAMILY = UpdateJob.class.getName();
private class UpdateJob extends Job {
public UpdateJob() {
super(CorextMessages.TypeInfoHistory_consistency_check);
}
@Override
protected IStatus run(IProgressMonitor monitor) {
internalCheckConsistency(monitor);
return new Status(IStatus.OK, DLTKUIPlugin.getPluginId(),
IStatus.OK, "", null); //$NON-NLS-1$
}
@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"; //$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 OpenTypeHistory(IDLTKUILanguageToolkit toolkit) {
super(FILENAME
+ toolkit.getCoreToolkit().getNatureId().replace('.', '_')
+ ".xml", NODE_ROOT, NODE_TYPE_INFO); //$NON-NLS-1$
fTimestampMapping = new HashMap<>();
fNeedsConsistencyCheck = true;
load();
fDeltaListener = new TypeHistoryDeltaListener();
DLTKCore.addElementChangedListener(fDeltaListener);
DLTKUIPlugin.getDefault().addShutdownListener(this);
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);
// this.fToolkit = toolkit;
this.fTypeFilter = new TypeFilter(toolkit);
}
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(FAMILY, monitor);
} catch (OperationCanceledException e) {
// Ignore and do the consistency check without
// waiting for the update job.
} catch (InterruptedException e) {
// Ignore and do the consistency check without
// waiting for the update job.
}
}
if (!fNeedsConsistencyCheck)
return;
internalCheckConsistency(monitor);
}
public synchronized boolean contains(TypeNameMatch type) {
return super.contains(type);
}
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,
Long.valueOf(getContainerTimestamp(info)));
}
super.accessed(info);
}
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,
Long.valueOf(getContainerTimestamp(newMatch)));
super.remove(old);
super.accessed(newMatch);
}
public synchronized TypeNameMatch[] getTypeInfos() {
Collection values = getValues();
int size = values.size();
TypeNameMatch[] result = new TypeNameMatch[size];
int i = size - 1;
for (Iterator iter = values.iterator(); iter.hasNext();) {
result[i] = (TypeNameMatch) iter.next();
i--;
}
return result;
}
public synchronized TypeNameMatch[] getFilteredTypeInfos(
TypeInfoFilter filter) {
Collection<?> values = getValues();
List<TypeNameMatch> result = new ArrayList<>();
for (Iterator<?> iter = values.iterator(); iter.hasNext();) {
TypeNameMatch type = (TypeNameMatch) iter.next();
if (type != null
&& (filter == null || filter.matchesHistoryElement(type))
&& !fTypeFilter.isFiltered(type.getFullyQualifiedName()))
result.add(type);
}
Collections.reverse(result);
return result.toArray(new TypeNameMatch[result.size()]);
}
@Override
protected Object getKey(Object object) {
return object;
}
private synchronized void internalCheckConsistency(IProgressMonitor monitor)
throws OperationCanceledException {
// Setting fNeedsConsistencyCheck is necessary here since
// markAsInconsistent isn't synchronized.
fNeedsConsistencyCheck = true;
List typesToCheck = new ArrayList(getKeys());
monitor.beginTask(CorextMessages.TypeInfoHistory_consistency_check,
typesToCheck.size());
monitor.setTaskName(CorextMessages.TypeInfoHistory_consistency_check);
for (Iterator iter = typesToCheck.iterator(); iter.hasNext();) {
TypeNameMatch type = (TypeNameMatch) iter.next();
if (type == null) {
continue;
}
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,
Long.valueOf(currentTimestamp));
}
}
} catch (ModelException 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.
IModelElement element = DLTKCore.create(resource);
if (element != null && element.exists())
return info.getLastModified();
}
}
} else { // external JAR
IProjectFragment root = match.getProjectFragment();
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) {
ISourceModule cu = match.getType().getSourceModule();
if (cu == null) {
return false;
}
IResource resource = cu.getResource();
if (resource != null) {
ITextFileBufferManager manager = FileBuffers
.getTextFileBufferManager();
ITextFileBuffer textFileBuffer = manager.getTextFileBuffer(
resource.getFullPath(), LocationKind.NORMALIZE);
if (textFileBuffer != null) {
return textFileBuffer.isDirty();
}
}
return false;
}
@Override
public void shutdown() {
DLTKCore.removeElementChangedListener(fDeltaListener);
save();
}
@Override
protected Object createFromElement(Element type) {
String handle = type.getAttribute(NODE_HANDLE);
if (handle == null)
return null;
IModelElement element = DLTKCore.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, Long.valueOf(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());
}
}
}