blob: 6fe98a65343d6f4c4cd6cca862a27778ce8f20dd [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2010, 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.internal.rhelp.core;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.jcommons.status.ErrorStatus;
import org.eclipse.statet.jcommons.status.InfoStatus;
import org.eclipse.statet.jcommons.status.Status;
import org.eclipse.statet.jcommons.status.StatusException;
import org.eclipse.statet.jcommons.status.Statuses;
import org.eclipse.statet.internal.rhelp.core.server.ServerClientSupport;
import org.eclipse.statet.internal.rhelp.core.server.ServerREnvHelpAccess;
import org.eclipse.statet.rhelp.core.REnvHelpConfiguration;
import org.eclipse.statet.rhelp.core.RHelpCore;
import org.eclipse.statet.rj.renv.core.REnv;
import org.eclipse.statet.rj.renv.core.REnvConfiguration;
import org.eclipse.statet.rj.renv.core.REnvManager;
@NonNullByDefault
public class RHelpManagerIntern {
private static @Nullable Object getStateLocation(final REnvHelpConfiguration config) {
switch (config.getStateSharedType()) {
case REnvConfiguration.SHARED_DIRECTORY:
return config.getStateSharedDirectoryPath();
case REnvConfiguration.SHARED_SERVER:
return config.getStateSharedServerUri();
default:
return null;
}
}
private static boolean isConfigEqual(final REnvHelpConfiguration config1,
final @Nullable REnvHelpConfiguration config2) {
return (config2 != null
&& config1.getStateSharedType() == config2.getStateSharedType()
&& Objects.equals(getStateLocation(config1), getStateLocation(config2)) );
}
private static final int HELP_LOADED= 2;
private static final int HELP_AVAILABLE= 1;
private static final int UNKNOWN= 0;
private static final int HELP_MISSING= -1;
private static final int RENV_DELETED= -2;
private static class EnvItem {
final REnv rEnv;
int state;
long localStamp;
@Nullable REnvHelpImpl help;
private String configStateType;
private @Nullable Object configStateLocation;
final Object helpLock= new Object();
boolean indexUpdate;
boolean indexCheck;
@Nullable Throwable lastServerException;
public EnvItem(final REnv rEnv) {
this.rEnv= rEnv;
updateConfig(null);
unset((rEnv.isDeleted()) ? RENV_DELETED : UNKNOWN);
}
public boolean isConfigured() {
return (this.state == HELP_LOADED || this.state == HELP_MISSING);
}
public void updateConfig(final @Nullable REnvHelpConfiguration config) {
final String newStateType;
final Object newStateLocation;
if (config != null) {
newStateType= config.getStateSharedType();
newStateLocation= getStateLocation(config);
}
else {
newStateType= ""; //$NON-NLS-1$
newStateLocation= null;
}
if (newStateType != this.configStateType
|| !Objects.equals(newStateLocation, this.configStateLocation) ) {
this.configStateType= newStateType;
this.configStateLocation= newStateLocation;
this.lastServerException= null;
}
}
public void updateLocalStamp(final long stamp) {
assert (this.help == null);
this.state= (stamp != REnvHelpImpl.NOT_AVAILABLE_STAMP) ?
HELP_AVAILABLE : HELP_MISSING;
this.localStamp= stamp;
}
public @Nullable REnvHelpImpl set(final @Nullable REnvHelpImpl help) {
if (help == null) {
return unset(HELP_MISSING);
}
final REnvHelpImpl oldHelp= this.help;
this.state= HELP_LOADED;
this.localStamp= help.getStamp();
this.help= help;
return oldHelp;
}
public @Nullable REnvHelpImpl unset(final int state) {
final REnvHelpImpl oldHelp= this.help;
this.state= state;
this.localStamp= REnvHelpImpl.NOT_AVAILABLE_STAMP;
this.help= null;
return oldHelp;
}
public boolean isConfigEqual(final @Nullable REnvHelpConfiguration config) {
return (config != null
&& this.configStateType == config.getStateSharedType()
&& Objects.equals(this.configStateLocation, getStateLocation(config)));
}
}
private static boolean isEquals(final @Nullable Throwable e1, final @Nullable Throwable e2) {
if (e1 == e2) {
return true;
}
if (e1 != null && e2 != null) {
return (e1.getClass() == e2.getClass()
&& Objects.equals(e1.getMessage(), e2.getMessage())
&& isEquals(e1.getCause(), e2.getCause()) );
}
return false;
}
protected final REnvManager rEnvManager;
private final Object indexLock= new Object();
private final Map<String, EnvItem> helpIndexes= new HashMap<>();
private final SerUtil serUtil= new SerUtil();
public RHelpManagerIntern(final REnvManager rEnvManager) {
this.rEnvManager= rEnvManager;
}
public @Nullable REnv getREnv(final @Nullable String id) {
return this.rEnvManager.get(id, null);
}
private EnvItem getItem(final REnv rEnv) {
synchronized (this.indexLock) {
EnvItem item= this.helpIndexes.get(rEnv.getId());
if (item == null) {
item= new EnvItem(rEnv);
this.helpIndexes.put(rEnv.getId(), item);
}
return item;
}
}
private ImList<EnvItem> getItems() {
return ImCollections.toList(this.helpIndexes.values());
}
public @Nullable Object beginIndexUpdate(final REnv rEnv) {
final EnvItem item= getItem(rEnv);
synchronized (item.helpLock) {
if (item.indexUpdate) {
return null;
}
else {
item.indexUpdate= true;
return item;
}
}
}
public void endIndexUpdate(final Object lock) {
final EnvItem item= (EnvItem) lock;
synchronized (item.helpLock) {
item.indexUpdate= false;
}
}
public @Nullable Object beginIndexCheck(final REnv rEnv) {
final EnvItem item= getItem(rEnv);
synchronized (item.helpLock) {
if (item.indexCheck || item.indexUpdate) {
return null;
}
else {
item.indexCheck= true;
return item;
}
}
}
public void endIndexCheck(final Object lock) {
final EnvItem item= (EnvItem) lock;
synchronized (item.helpLock) {
item.indexCheck= false;
}
}
public void checkREnvs() {
final List<EnvItem> items;
final ImList<? extends REnv> rEnvs= this.rEnvManager.list();
synchronized (this.indexLock) {
for (final REnv rEnv : rEnvs) {
getItem(rEnv);
}
items= new ArrayList<>(this.helpIndexes.values());
}
for (final EnvItem item : items) {
REnvHelpImpl oldHelp= null;
try {
synchronized (item.helpLock) {
if (item.rEnv.isDeleted()) {
oldHelp= item.unset(RENV_DELETED);
}
else {
final REnvHelpConfiguration rEnvConfig= item.rEnv.get(REnvHelpConfiguration.class);
if (item.isConfigured()) {
if (item.isConfigEqual(rEnvConfig)) {
continue;
}
else {
oldHelp= item.unset(UNKNOWN);
}
}
item.updateConfig(rEnvConfig);
}
}
}
finally {
if (oldHelp != null) {
oldHelp.dispose();
}
}
}
}
private static class SerSaveController implements SerUtil.Controller {
private final EnvItem item;
private final REnvHelpConfiguration rEnvConfig;
protected @Nullable REnvHelpImpl oldHelp;
public SerSaveController(final EnvItem item, final REnvHelpConfiguration rEnvConfig) {
this.item= item;
this.rEnvConfig= rEnvConfig;
}
public EnvItem getItem() {
return this.item;
}
@Override
public Object getFileLock() {
return this.item.helpLock;
}
@Override
public boolean shouldSave() {
return (this.item.state != RENV_DELETED
&& isConfigEqual(this.rEnvConfig, this.item.rEnv.get(REnvHelpConfiguration.class)) );
}
@Override
public void onSaved() {
}
public void dispose() {
if (this.oldHelp != null) {
this.oldHelp.dispose();
}
}
}
public boolean updateLocalHelp(final REnvHelpConfiguration rEnvConfig,
final @Nullable Map<String, String> rEnvSharedProperties, final REnvHelpImpl newHelp) {
final REnv rEnv= newHelp.getREnv();
final SerSaveController controller= new SerSaveController(getItem(rEnv), rEnvConfig) {
@Override
public void onSaved() {
final EnvItem item= getItem();
item.updateConfig(rEnvConfig);
this.oldHelp= item.set(newHelp);
// TODO REMOTE
// if (rEnvConfig instanceof REnvConfiguration) {
// ((REnvConfiguration) rEnvConfig).updateSharedProperties(rEnvSharedProperties);
// }
}
};
try {
this.serUtil.save(rEnvConfig, newHelp, controller);
return true;
}
finally {
controller.dispose();
}
}
public void updateServerHelp() {
try {
final ServerClientSupport serverSupport= ServerClientSupport.getInstance();
final ImList<EnvItem> items= getItems();
for (final EnvItem item : items) {
if (item.rEnv.isDeleted()) {
continue;
}
updateServerHelp(item, serverSupport);
}
}
catch (final Exception e) {
RHelpCoreInternals.log(new ErrorStatus(RHelpCore.BUNDLE_ID,
"An error occured while updating R help data for R environments.",
e ));
}
}
private void updateServerHelp(final EnvItem item, final ServerClientSupport serverSupport) {
final REnvHelpConfiguration rEnvConfig;
long currentStamp;
synchronized (item.helpLock) {
if (item.configStateType != REnvConfiguration.SHARED_SERVER) {
return;
}
rEnvConfig= item.rEnv.get(REnvHelpConfiguration.class);
if (rEnvConfig == null) {
return;
}
if (item.state == UNKNOWN) {
item.updateLocalStamp(this.serUtil.getStamp(rEnvConfig));
}
currentStamp= item.localStamp;
}
try {
final ServerREnvHelpAccess serverAccess= serverSupport.getREnvHelpAccess(rEnvConfig);
if (serverAccess.loadREnvHelpData(rEnvConfig, currentStamp,
new SerSaveController(item, rEnvConfig) )) {
REnvHelpImpl newHelp= null;
REnvHelpImpl oldHelp= null;
try {
synchronized (item.helpLock) {
switch (item.state) {
case RENV_DELETED:
break;
case HELP_LOADED:
item.updateConfig(rEnvConfig);
newHelp= this.serUtil.load(rEnvConfig);
oldHelp= item.set(newHelp);
break;
default:
item.updateLocalStamp(this.serUtil.getStamp(rEnvConfig));
break;
}
}
if (newHelp != null) {
newHelp.setIndex(serverAccess);
}
}
finally {
if (oldHelp != null) {
oldHelp.dispose();
}
}
RHelpCoreInternals.log(new InfoStatus(RHelpCore.BUNDLE_ID,
String.format("Successfully updated R help data for R environment '%1$s'.",
item.rEnv.getName() )));
}
}
catch (final Exception e) {
if (isEquals(item.lastServerException, e)) {
return;
}
item.lastServerException= e;
byte severity= Status.ERROR;
String message= null;
if (e instanceof StatusException) {
final Status status= ((StatusException) e).getStatus();
switch (status.getCode()) {
// case REnvHelpIndex.TIMEOUT_ERROR:
case REnvHelpIndex.CONNECT_ERROR:
severity= Status.INFO;
message= String.format("Updating R help data for R environment '%1$s' failed. " +
"It seems the R help server is currently not available.",
item.rEnv.getName() );
break;
default:
severity= status.getSeverity();
break;
}
}
RHelpCoreInternals.log(Statuses.newStatus(severity, RHelpCore.BUNDLE_ID,
(message != null) ? message :
String.format("An error occured when updating R help data for R environment '%1$s'.",
item.rEnv.getName() ),
e ));
}
}
public List<REnv> getREnvWithHelpIntern() {
final ImList<EnvItem> items= getItems();
final List<REnv> withHelp= new ArrayList<>(items.size());
for (final EnvItem item : items) {
if (item.rEnv.isDeleted()) {
continue;
}
if (hasHelp(item)) {
withHelp.add(item.rEnv);
}
}
return withHelp;
}
public boolean hasHelpIntern(final REnv rEnv) {
if (rEnv.isDeleted()) {
return false;
}
final EnvItem item= getItem(rEnv);
return hasHelp(item);
}
private boolean hasHelp(final EnvItem item) {
synchronized (item.helpLock) {
if (item.state == UNKNOWN) {
final REnvHelpConfiguration rEnvConfig= item.rEnv.get(REnvHelpConfiguration.class);
item.updateLocalStamp((rEnvConfig != null) ?
this.serUtil.getStamp(rEnvConfig) : REnvHelpImpl.NOT_AVAILABLE_STAMP);
}
switch (item.state) {
case HELP_LOADED:
case HELP_AVAILABLE:
return true;
default:
return false;
}
}
}
public @Nullable REnvHelpImpl getHelpIntern(final REnv rEnv) {
final EnvItem item= getItem(rEnv);
synchronized (item.helpLock) {
switch (item.state) {
case HELP_LOADED:
item.help.lock();
return item.help;
case HELP_AVAILABLE:
case UNKNOWN:
final REnvHelpConfiguration rEnvConfig= rEnv.get(REnvHelpConfiguration.class);
if (rEnvConfig != null) {
item.updateConfig(rEnvConfig);
item.set(this.serUtil.load(rEnvConfig));
}
if (item.help != null) {
item.help.lock();
return item.help;
}
else {
return null;
}
default:
return null;
}
}
}
}