Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
preferences.setValue(ICoreConstants.WORKSPACE_TARGET_HANDLE, ""); //$NON-NLS-1$
}
preferences.setValue(ICoreConstants.WORKSPACE_TARGET_HANDLE, memento);
storeActiveTargetFileStamp(preferences);


loadJRE(subMon.split(3));
Expand Down Expand Up @@ -181,6 +182,19 @@ private void loadJRE(IProgressMonitor monitor) {
monitor.done();
}

/**
* Records the backing file's modification time so the next session can detect
* external changes (e.g. {@code git pull}); cleared for non-file targets.
*/
private void storeActiveTargetFileStamp(PDEPreferencesManager preferences) {
java.io.File file = fTarget == null ? null : TargetPlatformService.backingFile(fTarget.getHandle());
if (file != null) {
preferences.setValue(ICoreConstants.WORKSPACE_TARGET_FILE_STAMP, Long.toString(file.lastModified()));
} else {
preferences.setToDefault(ICoreConstants.WORKSPACE_TARGET_FILE_STAMP);
}
}

/**
* Resets the PDE workspace models with the new state information
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,13 @@ public interface ICoreConstants {
*/
String WORKSPACE_TARGET_HANDLE = "workspace_target_handle"; //$NON-NLS-1$

/**
* Preference key holding the modification timestamp of the active workspace
* target file at its last load, used on startup to detect external edits
* (e.g. a {@code git pull}) and reload automatically.
*/
String WORKSPACE_TARGET_FILE_STAMP = "workspace_target_file_stamp"; //$NON-NLS-1$

/**
* Preference key for the workspace bundle overriding target bundle for the
* same id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ public void start(BundleContext context) throws Exception {

fTargetPlatformService = context.registerService(ITargetPlatformService.class,
TargetPlatformService.getDefault(), new Hashtable<>());
((TargetPlatformService) TargetPlatformService.getDefault()).start();
fBundleProjectService = context.registerService(IBundleProjectService.class, BundleProjectService.getDefault(),
new Hashtable<>());

Expand Down Expand Up @@ -446,6 +447,7 @@ public void stop(BundleContext context) throws CoreException {
PluginModelManager.shutdownInstance();

if (fTargetPlatformService != null) {
((TargetPlatformService) TargetPlatformService.getDefault()).stop();
fTargetPlatformService.unregister();
fTargetPlatformService = null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public class Messages extends NLS {

public static String TargetPlatformService_6;
public static String TargetPlatformService_7;
public static String TargetPlatformService_8;
public static String WorkspaceFileTargetHandle_0;
public static String TargetRefrenceBundleContainer_Failure;
public static String TargetRefrenceLocationFactory_Unsupported_Type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ TargetPlatformService_3=Error reading target extension file: {0}
TargetPlatformService_4=Target extension file does not exist: {0}
TargetPlatformService_6=Setting VM arguments
TargetPlatformService_7=Running Platform
TargetPlatformService_8=Reloading target platform
WorkspaceFileTargetHandle_0=Unable to generate memento for target platform
TargetRefrenceBundleContainer_Failure=Resolving referenced target failed
TargetRefrenceLocationFactory_Unsupported_Type=Type {0} is not supported by this Target Location Factory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
Expand All @@ -64,6 +67,7 @@
import org.eclipse.pde.core.target.ITargetHandle;
import org.eclipse.pde.core.target.ITargetLocation;
import org.eclipse.pde.core.target.ITargetPlatformService;
import org.eclipse.pde.core.target.LoadTargetDefinitionJob;
import org.eclipse.pde.core.target.NameVersionDescriptor;
import org.eclipse.pde.core.target.TargetBundle;
import org.eclipse.pde.core.target.TargetEvents;
Expand Down Expand Up @@ -103,6 +107,29 @@ public class TargetPlatformService implements ITargetPlatformService {
*/
private StringBuilder fVMArguments;

/** Path of the active target's backing workspace file, or {@code null}; watched for changes. */
private volatile IPath fActiveTargetFilePath;

private IResourceChangeListener fActiveTargetFileListener;

/** Delay used to coalesce reloads triggered by changes to the active target's file. */
private static final long RELOAD_COALESCE_DELAY_MS = 500;

/**
* Debounced reload job: rescheduling on each change coalesces a burst of file
* events into a single {@link LoadTargetDefinitionJob}.
*/
private final Job fReloadJob = Job.create(Messages.TargetPlatformService_8, monitor -> {
try {
ITargetHandle handle = getWorkspaceTargetHandle();
if (handle != null && handle.exists()) {
LoadTargetDefinitionJob.load(handle.getTargetDefinition());
}
} catch (CoreException e) {
PDECore.log(e);
}
});

private TargetPlatformService() {
}

Expand Down Expand Up @@ -330,11 +357,110 @@ public synchronized ITargetDefinition getWorkspaceTargetDefinition() throws Core
*/
public void setWorkspaceTargetDefinition(ITargetDefinition target, boolean asyncEvents) {
ITargetDefinition oldTarget = fWorkspaceTarget.getAndSet(target);
refreshActiveTargetFilePath(target);
if (!Objects.equals(oldTarget, target)) {
notifyEvent(TargetEvents.TOPIC_WORKSPACE_TARGET_CHANGED, target, asyncEvents);
}
}

private void refreshActiveTargetFilePath(ITargetDefinition target) {
if (target != null && target.getHandle() instanceof WorkspaceFileTargetHandle wsHandle) {
fActiveTargetFilePath = wsHandle.getTargetFile().getFullPath();
} else {
fActiveTargetFilePath = null;
}
}

/**
* Starts watching the active target's backing file and reloads if it changed
* since the last session.
*/
public void start() {
if (fActiveTargetFileListener != null) {
return;
}
fActiveTargetFileListener = this::onResourceChanged;
ResourcesPlugin.getWorkspace().addResourceChangeListener(fActiveTargetFileListener,
IResourceChangeEvent.POST_CHANGE);
reloadIfActiveTargetFileChangedSinceLastSession();
}

/**
* Reloads if the active target's backing file changed since its last load
* (e.g. a {@code git pull} while the IDE was closed), which resource deltas
* cannot detect.
*/
private void reloadIfActiveTargetFileChangedSinceLastSession() {
try {
ITargetHandle handle = getWorkspaceTargetHandle();
File file = backingFile(handle);
if (file == null || !file.isFile()) {
return;
}
PDEPreferencesManager preferences = PDECore.getDefault().getPreferencesManager();
String storedStamp = preferences.getString(ICoreConstants.WORKSPACE_TARGET_FILE_STAMP);
if (storedStamp == null || storedStamp.isEmpty()) {
// No baseline yet (first run, or older workspace). Don't trigger a
// reload here; the next explicit load will record the stamp.
return;
}
long previousStamp;
try {
previousStamp = Long.parseLong(storedStamp);
} catch (NumberFormatException e) {
return;
}
long currentStamp = file.lastModified();
if (currentStamp != 0 && currentStamp != previousStamp) {
scheduleReload();
}
} catch (CoreException e) {
PDECore.log(e);
}
}

/** Schedules a reload, coalescing calls within {@link #RELOAD_COALESCE_DELAY_MS}. */
private void scheduleReload() {
fReloadJob.cancel();
fReloadJob.schedule(RELOAD_COALESCE_DELAY_MS);
}

/**
* Returns the on-disk file backing the handle, or {@code null} for handles
* that are not file-backed.
*/
public static File backingFile(ITargetHandle handle) {
if (handle instanceof WorkspaceFileTargetHandle wsHandle) {
IPath location = wsHandle.getTargetFile().getLocation();
return location == null ? null : location.toFile();
}
if (handle instanceof ExternalFileTargetHandle extHandle) {
return URIUtil.toFile(extHandle.getLocation());
}
return null;
}

public void stop() {
if (fActiveTargetFileListener != null) {
ResourcesPlugin.getWorkspace().removeResourceChangeListener(fActiveTargetFileListener);
fActiveTargetFileListener = null;
}
fReloadJob.cancel();
}

private void onResourceChanged(IResourceChangeEvent event) {
IPath path = fActiveTargetFilePath;
if (path == null || event.getDelta() == null) {
return;
}
IResourceDelta delta = event.getDelta().findMember(path);
if (delta == null || delta.getKind() != IResourceDelta.CHANGED
|| (delta.getFlags() & IResourceDelta.CONTENT) == 0) {
return;
}
scheduleReload();
}

public static void scheduleEvent(String topic, Object data) {
notifyEvent(topic, data, true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,27 @@
*******************************************************************************/
package org.eclipse.pde.ui.tests.target;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.pde.core.target.ITargetDefinition;
import org.eclipse.pde.core.target.ITargetHandle;
import org.eclipse.pde.internal.core.target.TargetPlatformService;
import org.eclipse.pde.ui.tests.runtime.TestUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
* Tests for target definitions. The tested targets will be backed by a workspace file.
Expand Down Expand Up @@ -71,4 +81,66 @@ protected ITargetDefinition getNewTarget() {
return null;
}

/** {@code backingFile} resolves file-backed handles and returns {@code null} otherwise. */
@Test
public void testBackingFile() throws Exception {
// Workspace file handle resolves to the file inside the project.
IFile workspaceFile = ResourcesPlugin.getWorkspace().getRoot().getProject(PROJECT_NAME)
.getFile("backing.target");
ITargetHandle workspaceHandle = getTargetService().getTarget(workspaceFile);
assertEquals(workspaceFile.getLocation().toFile(), TargetPlatformService.backingFile(workspaceHandle));

// External file handle resolves to the file the URI points at.
Path external = Files.createTempFile("pde-backing", ".target");
try {
URI uri = external.toUri();
ITargetHandle externalHandle = getTargetService().getTarget(uri);
assertEquals(external.toFile(), TargetPlatformService.backingFile(externalHandle));
} finally {
Files.deleteIfExists(external);
}

// A fresh (local metadata) target is not backed by a file.
ITargetHandle localHandle = getTargetService().newTarget().getHandle();
assertNull(TargetPlatformService.backingFile(localHandle));
}

/** Editing the active target's workspace file triggers an automatic reload. */
@Test
public void testReloadOnWorkspaceFileChange() throws Exception {
IFile file = ResourcesPlugin.getWorkspace().getRoot().getProject(PROJECT_NAME).getFile("autoreload.target");
ITargetHandle handle = getTargetService().getTarget(file);
ITargetDefinition definition = handle.getTargetDefinition();
definition.setName("initial-name");
getTargetService().saveTargetDefinition(definition);
try {
setTargetPlatform(definition);
assertEquals("initial-name", getTargetService().getWorkspaceTargetDefinition().getName());

// Edit the backing file. The service watches it and should reload.
ITargetDefinition edited = handle.getTargetDefinition();
edited.setName("changed-name");
getTargetService().saveTargetDefinition(edited);

assertEquals("Target platform was not reloaded after its file changed", "changed-name",
waitForReloadedName("changed-name"));
} finally {
resetTargetPlatform();
}
}

/** Waits for the debounced reload to set the active target's name, returning what was observed. */
private String waitForReloadedName(String expected) throws CoreException {
long deadline = System.currentTimeMillis() + 30000;
String actual;
do {
// minTime must exceed the reload coalescing delay so the debounced
// load is scheduled before we wait for it to finish.
TestUtils.waitForJobs(name.getMethodName(), 700, 10000);
ITargetDefinition current = getTargetService().getWorkspaceTargetDefinition();
actual = current == null ? null : current.getName();
} while (!expected.equals(actual) && System.currentTimeMillis() < deadline);
return actual;
}

}
Loading