blob: 999196eed93af99815dc60e7fee5eaea908b8b63 [file] [log] [blame]
/*=============================================================================#
# 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);
}
}
}