| /*=============================================================================# |
| # Copyright (c) 2006, 2020 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.nico.core.runtime; |
| |
| import java.io.File; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.core.filesystem.IFileStore; |
| import org.eclipse.core.filesystem.URIUtil; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.variables.IDynamicVariable; |
| import org.eclipse.debug.core.DebugEvent; |
| import org.eclipse.debug.core.DebugPlugin; |
| |
| import org.eclipse.statet.jcommons.collections.CopyOnWriteIdentityListSet; |
| import org.eclipse.statet.jcommons.collections.ImCollections; |
| import org.eclipse.statet.jcommons.collections.ImList; |
| import org.eclipse.statet.jcommons.status.ErrorStatus; |
| import org.eclipse.statet.jcommons.status.ProgressMonitor; |
| import org.eclipse.statet.jcommons.status.StatusException; |
| import org.eclipse.statet.jcommons.ts.core.SystemRunnable; |
| import org.eclipse.statet.jcommons.ts.core.Tool; |
| import org.eclipse.statet.jcommons.ts.core.ToolService; |
| |
| import org.eclipse.statet.ecommons.io.FileUtil; |
| import org.eclipse.statet.ecommons.net.resourcemapping.core.ResourceMappingUtils; |
| import org.eclipse.statet.ecommons.runtime.core.util.PathUtils; |
| import org.eclipse.statet.ecommons.variables.core.DateVariable; |
| import org.eclipse.statet.ecommons.variables.core.DynamicVariable; |
| import org.eclipse.statet.ecommons.variables.core.TimeVariable; |
| |
| import org.eclipse.statet.internal.nico.core.NicoCorePlugin; |
| import org.eclipse.statet.nico.core.NicoCore; |
| import org.eclipse.statet.nico.core.NicoVariables; |
| import org.eclipse.statet.nico.core.runtime.ToolController.IToolStatusListener; |
| |
| |
| /** |
| * It belongs to a ToolProcess and has the same life cycle. |
| */ |
| public class ToolWorkspace { |
| |
| |
| public static interface Listener { |
| |
| public void propertyChanged(ToolWorkspace workspace, Map<String, Object> properties); |
| |
| } |
| |
| |
| private class ControllerListener implements IToolStatusListener { |
| |
| @Override |
| public void controllerStatusChanged(final ToolStatus oldStatus, final ToolStatus newStatus, final List<DebugEvent> eventCollection) { |
| if (newStatus == ToolStatus.TERMINATED) { |
| dispose(); |
| } |
| |
| // by definition in tool lifecycle thread |
| if (!newStatus.isRunning()) { |
| if (ToolWorkspace.this.currentPrompt == null || ToolWorkspace.this.currentPrompt == ToolWorkspace.this.publishedPrompt) { |
| return; |
| } |
| ToolWorkspace.this.publishedPrompt= ToolWorkspace.this.currentPrompt; |
| firePrompt(ToolWorkspace.this.currentPrompt, eventCollection); |
| return; |
| } |
| else { |
| ToolWorkspace.this.publishedPrompt= ToolWorkspace.this.defaultPrompt; |
| firePrompt(ToolWorkspace.this.defaultPrompt, eventCollection); |
| } |
| } |
| |
| } |
| |
| private class AutoUpdater implements SystemRunnable { |
| |
| |
| @Override |
| public String getTypeId() { |
| return "common/workspace/update.auto"; |
| } |
| |
| @Override |
| public String getLabel() { |
| return "Auto Update"; |
| } |
| |
| @Override |
| public boolean canRunIn(final Tool tool) { |
| return (tool == ToolWorkspace.this.process); |
| } |
| |
| @Override |
| public boolean changed(final int event, final Tool tool) { |
| switch (event) { |
| case REMOVING_FROM: |
| case MOVING_FROM: |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public void run(final ToolService service, final ProgressMonitor m) throws StatusException { |
| ToolWorkspace.this.isRefreshing= true; |
| try { |
| autoRefreshFromTool((ConsoleService) service, m); |
| } |
| finally { |
| ToolWorkspace.this.isRefreshing= false; |
| } |
| firePropertiesChanged(); |
| } |
| |
| } |
| |
| |
| public static final int DETAIL_PROMPT= 1; |
| public static final int DETAIL_LINE_SEPARTOR= 2; |
| |
| |
| private final ToolProcess process; |
| |
| private volatile String lineSeparator; |
| private volatile char fileSeparator; |
| |
| private volatile Prompt currentPrompt; |
| private volatile Prompt defaultPrompt; |
| private Prompt publishedPrompt; |
| |
| private IFileStore workspaceDir; |
| |
| private final String remoteHost; |
| private IPath remoteWorkspaceDirPath; |
| |
| private final Map<String, Object> properties= new HashMap<>(); |
| private final CopyOnWriteIdentityListSet<Listener> propertyListener= new CopyOnWriteIdentityListSet<>(); |
| |
| private boolean autoRefreshEnabled= true; |
| |
| private boolean isRefreshing; |
| |
| private final ImList<IDynamicVariable> stringVariables; |
| |
| private int changeFlags; |
| |
| |
| public ToolWorkspace(final ToolController controller, |
| Prompt prompt, final String lineSeparator, final char fileSeparator, |
| final String remoteHost) { |
| this.process= controller.getTool(); |
| if (prompt == null) { |
| prompt= Prompt.DEFAULT; |
| } |
| this.publishedPrompt= this.currentPrompt= this.defaultPrompt= prompt; |
| this.remoteHost= remoteHost; |
| controlSetLineSeparator(lineSeparator); |
| controlSetFileSeparator(fileSeparator); |
| |
| controller.addToolStatusListener(new ControllerListener()); |
| controller.getQueue().addOnIdle(new AutoUpdater(), 5000); |
| |
| this.stringVariables= ImCollections.<IDynamicVariable>newList( |
| new DateVariable(NicoVariables.SESSION_STARTUP_DATE_VARIABLE) { |
| @Override |
| protected long getTimestamp() { |
| return ToolWorkspace.this.process.getStartupTimestamp(); |
| } |
| }, |
| new TimeVariable(NicoVariables.SESSION_STARTUP_TIME_VARIABLE) { |
| @Override |
| protected long getTimestamp() { |
| return ToolWorkspace.this.process.getStartupTimestamp(); |
| } |
| }, |
| new DateVariable(NicoVariables.SESSION_CONNECTION_DATE_VARIABLE) { |
| @Override |
| protected long getTimestamp() { |
| return ToolWorkspace.this.process.getConnectionTimestamp(); |
| } |
| }, |
| new TimeVariable(NicoVariables.SESSION_CONNECTION_TIME_VARIABLE) { |
| @Override |
| protected long getTimestamp() { |
| return ToolWorkspace.this.process.getStartupTimestamp(); |
| } |
| }, |
| new DynamicVariable.LocationVariable(NicoVariables.SESSION_STARTUP_WD_VARIABLE) { |
| @Override |
| public String getValue(final String argument) throws CoreException { |
| return ToolWorkspace.this.process.getStartupWD(); |
| } |
| } ); |
| } |
| |
| |
| public ToolProcess getProcess() { |
| return this.process; |
| } |
| |
| public boolean isWindows() { |
| return (getFileSeparator() == '\\'); |
| } |
| |
| |
| public void setAutoRefresh(final boolean enable) { |
| synchronized (this.process.getQueue()) { |
| if (this.autoRefreshEnabled != enable) { |
| this.autoRefreshEnabled= enable; |
| final ToolStatus status= this.process.getToolStatus(); |
| if (status != ToolStatus.TERMINATED) { |
| if (enable && status.isWaiting()) { |
| this.process.getQueue().internal_resetOnIdle(); |
| this.process.getQueue().notifyAll(); |
| } |
| addPropertyChanged("AutoRefresh.enabled", enable); |
| firePropertiesChanged(); |
| } |
| } |
| } |
| } |
| |
| public boolean isAutoRefreshEnabled() { |
| return this.autoRefreshEnabled; |
| } |
| |
| |
| protected void autoRefreshFromTool(final ConsoleService service, |
| final ProgressMonitor m) throws StatusException { |
| if (this.autoRefreshEnabled) { |
| refreshFromTool(0, service, m); |
| } |
| } |
| |
| protected void refreshFromTool(final int options, final ConsoleService service, |
| final ProgressMonitor m) throws StatusException { |
| } |
| |
| |
| protected final int getChangeFlags() { |
| return this.changeFlags; |
| } |
| |
| protected void controlBriefChanged(final Object obj, final int flags) { |
| this.changeFlags|= flags; |
| } |
| |
| protected void clearBriefedChanges() { |
| this.changeFlags= 0; |
| } |
| |
| public final String getLineSeparator() { |
| return this.lineSeparator; |
| } |
| |
| public final char getFileSeparator() { |
| return this.fileSeparator; |
| } |
| |
| |
| public final Prompt getPrompt() { |
| return this.publishedPrompt; |
| } |
| |
| protected final Prompt getCurrentPrompt() { |
| return this.currentPrompt; |
| } |
| |
| public final Prompt getDefaultPrompt() { |
| return this.defaultPrompt; |
| } |
| |
| public final IFileStore getWorkspaceDir() { |
| return this.workspaceDir; |
| } |
| |
| public String getEncoding() { |
| return "UTF-8"; //$NON-NLS-1$ |
| } |
| |
| |
| public final boolean isRemote() { |
| return (this.remoteHost != null); |
| } |
| |
| public String getRemoteAddress() { |
| return this.remoteHost; |
| } |
| |
| public IPath getRemoteWorkspaceDirPath() { |
| return this.remoteWorkspaceDirPath; |
| } |
| |
| public IPath createToolPath(String toolPath) { |
| if (toolPath == null) { |
| return null; |
| } |
| if (isWindows() && File.separatorChar == '/') { |
| toolPath= toolPath.replace('\\', '/'); |
| } |
| return PathUtils.check(new Path(toolPath)); |
| } |
| |
| public IFileStore toFileStore(final IPath toolPath) throws CoreException { |
| if (this.remoteHost != null) { |
| return ResourceMappingUtils.getManager() |
| .mapRemoteResourceToFileStore(this.remoteHost, toolPath, |
| (this.remoteWorkspaceDirPath != null) ? this.remoteWorkspaceDirPath : null ); |
| } |
| return FileUtil.getFileStore(toolPath.toString(), this.workspaceDir); |
| } |
| |
| public IFileStore toFileStore(final String toolPath) throws CoreException { |
| if (this.remoteHost != null) { |
| return toFileStore(createToolPath(toolPath)); |
| } |
| return FileUtil.getFileStore(toolPath.toString(), this.workspaceDir); |
| } |
| |
| public String toToolPath(final IFileStore fileStore) throws StatusException { |
| if (this.remoteHost != null) { |
| final IPath path= ResourceMappingUtils.getManager() |
| .mapFileStoreToRemoteResource(this.remoteHost, fileStore); |
| if (path != null) { |
| return path.toString(); |
| } |
| throw new StatusException(new ErrorStatus(NicoCore.BUNDLE_ID, |
| "Resolving path for the remote system failed." )); |
| } |
| return URIUtil.toPath(fileStore.toURI()).toString(); |
| } |
| |
| |
| final void controlRefresh(final int options, final ConsoleService adapter, |
| final ProgressMonitor m) throws StatusException { |
| this.isRefreshing= true; |
| try { |
| refreshFromTool(options, adapter, m); |
| } |
| finally { |
| this.isRefreshing= false; |
| } |
| firePropertiesChanged(); |
| } |
| |
| |
| /** |
| * Use only in tool main thread. |
| * @param prompt the new prompt, null doesn't change anything |
| */ |
| final void controlSetCurrentPrompt(final Prompt prompt, final ToolStatus status) { |
| if (prompt == this.currentPrompt || prompt == null) { |
| return; |
| } |
| this.currentPrompt= prompt; |
| if (!status.isRunning()) { |
| this.publishedPrompt= prompt; |
| firePrompt(prompt, null); |
| } |
| } |
| |
| /** |
| * Use only in tool main thread. |
| * @param prompt the new prompt, null doesn't change anything |
| */ |
| final void controlSetDefaultPrompt(final Prompt prompt) { |
| if (prompt == this.defaultPrompt || prompt == null) { |
| return; |
| } |
| final Prompt oldDefault= this.defaultPrompt; |
| this.defaultPrompt= prompt; |
| if (oldDefault == this.currentPrompt) { |
| this.currentPrompt= prompt; |
| } |
| if (oldDefault == this.publishedPrompt) { |
| this.publishedPrompt= prompt; |
| firePrompt(prompt, null); |
| } |
| } |
| |
| /** |
| * Use only in tool main thread. |
| * |
| * The default separator is System.getProperty("line.separator") for local |
| * workspaces, and '\n' for remote workspaces. |
| * |
| * @param newSeparator the new line separator, null sets the default separator |
| */ |
| final void controlSetLineSeparator(final String newSeparator) { |
| final String oldSeparator= this.lineSeparator; |
| if (newSeparator != null) { |
| this.lineSeparator= newSeparator; |
| } |
| else { |
| this.lineSeparator= (this.remoteHost == null) ? System.getProperty("line.separator") : "\n"; //$NON-NLS-1$ |
| } |
| // if (!fLineSeparator.equals(oldSeparator)) { |
| // DebugEvent event= new DebugEvent(ToolWorkspace.this, DebugEvent.CHANGE, DETAIL_LINE_SEPARTOR); |
| // event.setData(fLineSeparator); |
| // fireEvent(event); |
| // } |
| } |
| |
| /** |
| * Use only in tool main thread. |
| * |
| * The default separator is System.getProperty("file.separator") for local |
| * workspaces, and '/' for remote workspaces. |
| * |
| * @param newSeparator the new file separator, null sets the default separator |
| */ |
| final void controlSetFileSeparator(final char newSeparator) { |
| final char oldSeparator= this.fileSeparator; |
| if (newSeparator != 0) { |
| this.fileSeparator= newSeparator; |
| } |
| else { |
| this.fileSeparator= (isRemote()) ? '/' : File.separatorChar; |
| } |
| } |
| |
| protected final void controlSetWorkspaceDir(final IFileStore directory) { |
| if ((this.workspaceDir != null) ? !this.workspaceDir.equals(directory) : directory != null) { |
| this.workspaceDir= directory; |
| this.properties.put("wd", directory); |
| if (!this.isRefreshing) { |
| firePropertiesChanged(); |
| } |
| } |
| } |
| |
| protected final void controlSetRemoteWorkspaceDir(final IPath toolPath) { |
| this.remoteWorkspaceDirPath= toolPath; |
| try { |
| controlSetWorkspaceDir(toFileStore(toolPath)); |
| } |
| catch (final CoreException e) { |
| controlSetWorkspaceDir(null); |
| } |
| } |
| |
| |
| private final void firePrompt(final Prompt prompt, final List<DebugEvent> eventCollection) { |
| final DebugEvent event= new DebugEvent(ToolWorkspace.this, DebugEvent.CHANGE, DETAIL_PROMPT); |
| event.setData(prompt); |
| if (eventCollection != null) { |
| eventCollection.add(event); |
| return; |
| } |
| else { |
| fireEvent(event); |
| } |
| } |
| |
| protected final void fireEvent(final DebugEvent event) { |
| final DebugPlugin manager= DebugPlugin.getDefault(); |
| if (manager != null) { |
| manager.fireDebugEventSet(new DebugEvent[] { event }); |
| } |
| } |
| |
| public final void addPropertyListener(final Listener listener) { |
| this.propertyListener.add(listener); |
| } |
| |
| public final void removePropertyListener(final Listener listener) { |
| this.propertyListener.remove(listener); |
| } |
| |
| protected final void addPropertyChanged(final String property, final Object attr) { |
| this.properties.put(property, attr); |
| } |
| |
| protected final void firePropertiesChanged() { |
| if (this.properties.isEmpty()) { |
| return; |
| } |
| for (final Listener listener : this.propertyListener.toList()) { |
| try { |
| listener.propertyChanged(ToolWorkspace.this, this.properties); |
| } |
| catch (final Exception e) { |
| NicoCorePlugin.logError(0, "An unexpected exception was thrown when notifying a tool workspace listener about changes.", e); |
| } |
| } |
| this.properties.clear(); |
| } |
| |
| public ImList<IDynamicVariable> getStringVariables() { |
| return this.stringVariables; |
| } |
| |
| protected void dispose() { |
| } |
| |
| } |