| /*=============================================================================# |
| # Copyright (c) 2017, 2019 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.rj.server.rh; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.logging.Level; |
| import java.util.logging.LogRecord; |
| import java.util.logging.Logger; |
| |
| 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.lang.NonNull; |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| |
| import org.eclipse.statet.rj.server.RjsException; |
| |
| |
| @NonNullByDefault |
| public class ObjectManager { |
| |
| |
| private static final Logger LOGGER= Logger.getLogger("org.eclipse.statet.rj.server"); //$NON-NLS-1$ |
| |
| |
| public static final char NO_REF= '0'; |
| public static final char STRONG_REF= 's'; |
| public static final char WEAK_REF= 'w'; |
| |
| |
| public static interface EnvListener { |
| |
| void onEnvRemoved(final RhEnv env, final boolean finalized); |
| |
| } |
| |
| public static interface SearchPathListener { |
| |
| void onSearchPathChanged(final List<RhEnv> searchPath, |
| final List<RhEnv> added, final List<RhEnv> removed); |
| |
| } |
| |
| |
| private static final String SEARCH_PATH_KEY= "#" + STRONG_REF + ":sys.SearchPath"; //$NON-NLS-1$ |
| private static final String STACK_FRAMES_KEY= "#" + NO_REF + ":sys.StackFrames"; //$NON-NLS-1$ |
| |
| private boolean isEqualHandles(final List<Handle> handles, final List<RhEnv> envs) { |
| final int l= handles.size(); |
| if (l == envs.size()) { |
| for (int i= 0; i < l; i++) { |
| if (!handles.get(i).equals(envs.get(i).handle)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| |
| protected final RhEngine engine; |
| |
| private final Map<Handle, RhEnv> registeredEnvs= new HashMap<>(); |
| private final RhRefListener envFinalizer; |
| private final CopyOnWriteIdentityListSet<EnvListener> envListeners= new CopyOnWriteIdentityListSet<>(); |
| |
| private ImList<RhEnv> searchPath= ImCollections.emptyList(); |
| private final CopyOnWriteIdentityListSet<SearchPathListener> searchPathListeners= new CopyOnWriteIdentityListSet<>(); |
| |
| private List<RhEnv> stackFrames= ImCollections.emptyList(); |
| |
| private final List<RhDisposable> toDispose= new ArrayList<>(); |
| |
| |
| public ObjectManager(final RhEngine engine) { |
| this.engine= engine; |
| |
| this.envFinalizer= new RhRefListener() { |
| @Override |
| public void onFinalized(final RhRef ref) { |
| final RhEnv env= ObjectManager.this.registeredEnvs.remove(ref.handle); |
| |
| if (env != null) { |
| onEnvRemoved(env, true); |
| } |
| } |
| }; |
| } |
| |
| |
| public RhEnv getEnv(final Handle handle) { |
| RhEnv env= this.registeredEnvs.get(handle); |
| if (env == null) { |
| env= new RhEnv(this.engine, handle, ObjectManager.this.envFinalizer); |
| this.registeredEnvs.put(handle, env); |
| } |
| return env; |
| } |
| |
| private void checkEnv(final RhEnv env) { |
| if (!env.isRegAny() |
| && this.registeredEnvs.remove(env.handle, env)) { |
| |
| onEnvRemoved(env, false); |
| } |
| } |
| |
| private void checkEnv(final RhEnv env, final Iterator<?> iter) { |
| if (!env.isRegAny()) { |
| iter.remove(); |
| |
| onEnvRemoved(env, false); |
| } |
| } |
| |
| private void onEnvRemoved(final RhEnv env, final boolean finalized) { |
| env.dispose(); |
| |
| for (final EnvListener listener : this.envListeners) { |
| try { |
| listener.onEnvRemoved(env, finalized); |
| } |
| catch (final Exception e) { |
| final LogRecord record= new LogRecord(Level.SEVERE, |
| "An error occurred in EnvListener.onEnvRemoved({0}, {1})."); |
| record.setParameters(new Object[] { |
| env, |
| finalized |
| }); |
| record.setThrown(e); |
| LOGGER.log(record); |
| } |
| } |
| } |
| |
| public RhEnv registerEnv(final Handle handle, final String key) { |
| final RhEnv env= getEnv(handle); |
| env.addReg(key); |
| return env; |
| } |
| |
| public @Nullable RhEnv unregisterEnv(final Handle handle, final String key) { |
| final RhEnv env= this.registeredEnvs.get(handle); |
| if (env != null && env.removeReg(key)) { |
| checkEnv(env); |
| return env; |
| } |
| return null; |
| } |
| |
| public void unregisterEnvs(final String key) { |
| for (final Iterator<RhEnv> iter= this.registeredEnvs.values().iterator(); iter.hasNext();) { |
| final RhEnv env= iter.next(); |
| if (env.removeReg(key)) { |
| checkEnv(env, iter); |
| } |
| } |
| } |
| |
| public Collection<RhEnv> getEnvs() { |
| return this.registeredEnvs.values(); |
| } |
| |
| |
| public @Nullable RhEnv getParentEnv(final RhEnv env) throws RjsException { |
| final Handle handle= this.engine.getParentEnv(env.handle); |
| return (handle != null) ? getEnv(handle) : null; |
| } |
| |
| public void addEnvListener(final EnvListener listener) { |
| this.envListeners.add(listener); |
| } |
| |
| public void removeEnvListener(final EnvListener listener) { |
| this.envListeners.remove(listener); |
| } |
| |
| |
| public void clean() { |
| for (final Iterator<RhEnv> iter= this.registeredEnvs.values().iterator(); iter.hasNext();) { |
| final RhEnv env= iter.next(); |
| checkEnv(env, iter); |
| } |
| } |
| |
| |
| public ImList<RhEnv> getSearchPath() { |
| return this.searchPath; |
| } |
| |
| public void updateSearchPath() throws RjsException { |
| final List<Handle> handles= this.engine.getSearchPath(); |
| if (isEqualHandles(handles, this.searchPath)) { |
| return; |
| } |
| final RhEnv[] envs= new @NonNull RhEnv[handles.size()]; |
| final List<RhEnv> addedEnvs= new ArrayList<>(); |
| final List<RhEnv> removedEnvs= new ArrayList<>(this.searchPath); |
| for (int i= 0; i < envs.length; i++) { |
| final Handle handle= handles.get(i); |
| final RhEnv env= getEnv(handle); |
| if (env.addReg(SEARCH_PATH_KEY)) { |
| addedEnvs.add(env); |
| } |
| else { |
| removedEnvs.remove(env); |
| } |
| envs[i]= env; |
| } |
| this.searchPath= ImCollections.newList(envs); |
| |
| onSearchPathChanged(this.searchPath, addedEnvs, removedEnvs); |
| |
| for (final RhEnv env : removedEnvs) { |
| if (env.removeReg(SEARCH_PATH_KEY)) { // true |
| checkEnv(env); |
| } |
| } |
| } |
| |
| private void onSearchPathChanged(final List<RhEnv> searchPath, |
| final List<RhEnv> added, final List<RhEnv> removed) { |
| for (final SearchPathListener listener : this.searchPathListeners) { |
| try { |
| listener.onSearchPathChanged(searchPath, added, removed); |
| } |
| catch (final Exception e) { |
| LOGGER.log(Level.SEVERE, "An error occurred.", e); |
| } |
| } |
| } |
| |
| public void updateStackFrames() throws RjsException { |
| final List<Handle> handles= this.engine.getStackFrames(); |
| if (isEqualHandles(handles, this.stackFrames)) { |
| return; |
| } |
| final RhEnv[] envs= new @NonNull RhEnv[handles.size()]; |
| final List<RhEnv> addedEnvs= new ArrayList<>(); |
| final List<RhEnv> removedEnvs= new ArrayList<>(this.stackFrames); |
| for (int i= 0; i < envs.length; i++) { |
| final Handle handle= handles.get(i); |
| final RhEnv env= getEnv(handle); |
| if (env.addReg(STACK_FRAMES_KEY)) { |
| addedEnvs.add(env); |
| } |
| else { |
| removedEnvs.remove(env); |
| } |
| envs[i]= env; |
| } |
| this.stackFrames= ImCollections.newList(envs); |
| |
| for (final RhEnv env : removedEnvs) { |
| if (env.removeReg(STACK_FRAMES_KEY)) { // true |
| checkEnv(env); |
| } |
| } |
| } |
| |
| public List<RhEnv> getStackFrames() { |
| return this.stackFrames; |
| } |
| |
| |
| public void update() throws RjsException { |
| clean(); |
| updateSearchPath(); |
| updateStackFrames(); |
| } |
| |
| |
| public void addToDispose(final RhDisposable disposable) { |
| synchronized (this) { |
| this.toDispose.add(disposable); |
| } |
| } |
| |
| public void runJobs() { |
| final ImList<RhDisposable> disposables; |
| synchronized (this) { |
| disposables= ImCollections.clearToList(this.toDispose); |
| } |
| for (final RhDisposable disposable : disposables) { |
| disposable.dispose(this.engine); |
| } |
| } |
| |
| } |