blob: 516d5239c842f3bd74ec70ac18ba50fa8ef38377 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2010, 2021 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.rhelp.core.update;
import static org.eclipse.statet.internal.rhelp.core.RHelpCoreInternals.DEBUG;
import static org.eclipse.statet.internal.rhelp.core.index.RHelpHtmlUtils.HR_LINE_PATTERN;
import static org.eclipse.statet.internal.rhelp.core.index.RHelpHtmlUtils.HR_PREFIX;
import static org.eclipse.statet.rhelp.core.RHelpCore.BUNDLE_ID;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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.ProgressMonitor;
import org.eclipse.statet.jcommons.status.Status;
import org.eclipse.statet.jcommons.status.StatusException;
import org.eclipse.statet.jcommons.status.WarningStatus;
import org.eclipse.statet.internal.rhelp.core.RHelpWebapp;
import org.eclipse.statet.internal.rhelp.core.index.AbortIndexOperationException;
import org.eclipse.statet.internal.rhelp.core.index.RDocResource;
import org.eclipse.statet.internal.rhelp.core.index.REnvIndexWriter;
import org.eclipse.statet.internal.rhelp.core.index.REnvIndexWriter.RdItem;
import org.eclipse.statet.rhelp.core.REnvHelpConfiguration;
import org.eclipse.statet.rhelp.core.RHelpManager;
import org.eclipse.statet.rj.data.RCharacterStore;
import org.eclipse.statet.rj.data.RDataUtils;
import org.eclipse.statet.rj.data.RList;
import org.eclipse.statet.rj.data.RObject;
import org.eclipse.statet.rj.data.RStore;
import org.eclipse.statet.rj.data.RVector;
import org.eclipse.statet.rj.renv.core.BasicRPkgDescription;
import org.eclipse.statet.rj.renv.core.RLibLocation;
import org.eclipse.statet.rj.renv.core.RNumVersion;
import org.eclipse.statet.rj.renv.core.RPkgBuilt;
import org.eclipse.statet.rj.renv.core.RPkgCompilation;
import org.eclipse.statet.rj.renv.core.RPkgDescription;
import org.eclipse.statet.rj.renv.runtime.RLibLocationInfo;
import org.eclipse.statet.rj.renv.runtime.RPkgManager;
import org.eclipse.statet.rj.renv.runtime.RPkgManagerDataset;
import org.eclipse.statet.rj.renv.runtime.RuntimeRLibPaths;
import org.eclipse.statet.rj.services.FunctionCall;
import org.eclipse.statet.rj.services.RService;
/**
* Updates the R environment (R help keywords, R help index).
* Uses the RService interface to read the data.
*/
@NonNullByDefault
public abstract class REnvIndexUpdater {
private static final String PKG_DESCR_FNAME= "rj:::rhelp.loadPkgDescr"; //$NON-NLS-1$
private static final int PKG_DESCR_LENGTH= 7;
private static final int PKG_DESCR_IDX_VERSION= 0;
private static final int PKG_DESCR_IDX_TITLE= 1;
private static final int PKG_DESCR_IDX_DESCRIPTION= 2;
private static final int PKG_DESCR_IDX_AUTHOR= 3;
private static final int PKG_DESCR_IDX_MAINTAINER= 4;
private static final int PKG_DESCR_IDX_URL= 5;
private static final int PKG_DESCR_IDX_BUILT= 6;
private static final String PKG_RD_FNAME= "rj:::rhelp.loadPkgRd"; //$NON-NLS-1$
private static String checkNA2Null(final String s) {
return (s != null && !s.equals("NA") && s.length() > 0) ? s : null; //$NON-NLS-1$
}
private static class PkgTask {
final String name;
final @Nullable RNumVersion version;
final @Nullable String built;
final RLibLocation libLocation;
final RLibLocationInfo libLocationInfo; // for error messages
RVector<RCharacterStore> rDescr;
RList rRd;
public PkgTask(final String name, final RNumVersion version, final String built,
final RLibLocation libLocation, final RLibLocationInfo libLocationInfo) {
this.name= name.intern();
this.version= version;
this.built= built;
this.libLocation= libLocation;
this.libLocationInfo= libLocationInfo;
}
@SuppressWarnings("null")
private PkgTask(final String signal) {
this.name= signal;
this.version= null;
this.built= null;
this.libLocation= null;
this.libLocationInfo= null;
}
@Override
public String toString() {
final StringBuilder sb= new StringBuilder(this.name);
sb.append(" " + "(version= ").append((this.version != null) ? this.version : "<unknown>");
sb.append(')');
if (this.libLocationInfo != null) {
sb.append(" in lib= '").append(this.libLocationInfo.getDirectoryRPath()).append('\'');
}
return sb.toString();
}
}
private static final PkgTask STOP_OK= new PkgTask("STOP_OK");
private static final PkgTask STOP_CANCEL= new PkgTask("STOP_CANCEL");
protected abstract class IndexJob {
private final BlockingQueue<PkgTask> queue= new ArrayBlockingQueue<>(4);
private volatile @Nullable Exception exception;
private int count;
private int queueCumCount;
public IndexJob() {
}
protected abstract void cancel();
protected abstract void join();
private void add(final PkgTask task, final ProgressMonitor m) throws Exception {
while (true) {
try {
if (this.exception != null) {
throw this.exception;
}
this.count++;
this.queueCumCount+= this.queue.size();
this.queue.put(task);
break;
}
catch (final InterruptedException e) {
if (m.isCanceled()) {
cancel();
throw new StatusException(Status.CANCEL_STATUS);
}
}
}
}
private void finish(final ProgressMonitor m) throws StatusException {
while (true) {
try {
this.queue.put(STOP_OK);
break;
}
catch (final InterruptedException e) {}
}
join();
}
private void cancel(final ProgressMonitor m) {
cancel();
while (true) {
try {
this.queue.put(STOP_CANCEL);
break;
}
catch (final InterruptedException e) {}
}
join();
}
public Status run(final ProgressMonitor monitor) {
try {
PkgTask task= null;
while (true) {
try {
task= this.queue.take();
if (task == STOP_OK) {
if (DEBUG) {
REnvIndexUpdater.this.index.log(new InfoStatus(BUNDLE_ID,
String.format("Fill level of queue: mean= %1.2f.",
(double) this.queueCumCount / this.count )));
}
return Status.OK_STATUS;
}
if (task == STOP_CANCEL) {
return Status.CANCEL_STATUS;
}
final RPkgDescription pkgDescription= createDescription(task);
REnvIndexUpdater.this.index.beginPackage(pkgDescription);
processRdData(pkgDescription.getName(), task.rRd);
}
catch (final InterruptedException e) {
// continue, monitor is checked
}
catch (final AbortIndexOperationException e) {
this.exception= e;
this.queue.clear();
return Status.CANCEL_STATUS;
}
catch (final Exception e) {
REnvIndexUpdater.this.index.log(new ErrorStatus(BUNDLE_ID,
String.format("An error occurred when indexing data for package: %1$s",
task ),
e ));
}
finally {
try {
REnvIndexUpdater.this.index.endPackage();
}
catch (final Exception e) {
this.exception= e;
this.queue.clear();
return Status.CANCEL_STATUS;
}
}
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
}
}
catch (final Exception e) {
this.exception= e;
this.queue.clear();
return Status.CANCEL_STATUS;
}
}
}
private final REnvHelpConfiguration rEnvConfig;
private final StringBuilder tempBuilder1= new StringBuilder(65536);
private final StringBuilder tempBuilder2= new StringBuilder(1024);
private final REnvIndexWriter index;
private final RPkgManager rPkgManager;
public REnvIndexUpdater(final REnvHelpConfiguration rEnvConfig,
final RHelpManager rHelpManager, final RPkgManager rPkgManager) {
this.rEnvConfig= rEnvConfig;
this.index= new REnvIndexWriter(rEnvConfig, rHelpManager);
this.rPkgManager= rPkgManager;
}
protected RPkgManager getRPkgManager() {
return this.rPkgManager;
}
public Status update(final RService r, final boolean reset,
final @Nullable Map<String, String> rEnvSharedProperties,
final ProgressMonitor m) {
m.setWorkRemaining(1 + 8 + 1);
try {
this.index.beginBatch(reset);
final String docDir= checkNA2Null(RDataUtils.checkSingleChar(
r.evalData("R.home(\"doc\")", m) )); //$NON-NLS-1$
this.index.setDocDir(docDir, (docDir != null) ? toLocalPath(docDir, r, m) : null);
this.index.addManuals(RDocResource.R_MANUALS);
this.index.addMiscResources(RDocResource.R_MISCS);
this.index.setREnvSharedProperties(rEnvSharedProperties);
long tKeywords= System.nanoTime();
loadKeywords(r, m.newSubMonitor(1));
tKeywords= System.nanoTime() - tKeywords;
m.checkCanceled();
long tPackages= System.nanoTime();
loadPackages(r, m.newSubMonitor(8));
tPackages= System.nanoTime() - tPackages;
if (DEBUG) {
this.index.log(new InfoStatus(BUNDLE_ID,
String.format("Required time for update: keywords= %1$sms, packages= %2$sms.",
tKeywords / 1_000_000, tPackages / 1_000_000 )));
}
final Status status= this.index.endBatch();
if (status != null && status.getSeverity() >= Status.ERROR) {
return new WarningStatus(BUNDLE_ID,
"The R environment index could not be completely updated.",
new StatusException(status) );
}
return new InfoStatus(BUNDLE_ID,
"The R environment index was updated successfully." );
}
catch (final StatusException e) {
if (e.getStatus().getSeverity() == Status.CANCEL) {
this.index.cancel();
return e.getStatus();
}
this.index.log(new ErrorStatus(BUNDLE_ID,
"An error occurred when updating the R environment.",
e ));
final Status status= this.index.cancel();
return new ErrorStatus(BUNDLE_ID,
"The R environment could not be updated.",
(status != null) ? new StatusException(status) : null );
}
catch (final Exception e) {
this.index.log(new ErrorStatus(BUNDLE_ID,
"An error occurred when updating the R environment.",
e ));
final Status status= this.index.cancel();
return new ErrorStatus(BUNDLE_ID,
"The R environment could not be updated.",
(status != null) ? new StatusException(status) : null );
}
}
private void loadKeywords(final RService r, final ProgressMonitor m) throws StatusException {
final String docDir= this.index.getDocDir();
if (docDir == null) {
return;
}
m.beginTask("Loading R help keywords...", 10 + 10);
Exception errorCause= null;
try {
final byte[] bytes= r.downloadFile(docDir + "/KEYWORDS.db", 0, m.newSubMonitor(10));
final BufferedReader reader= new BufferedReader(new InputStreamReader(
new ByteArrayInputStream(bytes), "UTF-8")); //$NON-NLS-1$
String line;
this.tempBuilder1.setLength(0);
while ((line= reader.readLine()) != null) {
if (DEBUG) {
this.tempBuilder1.append(line);
this.tempBuilder1.append('\n');
}
int idx= line.indexOf('#');
if (idx >= 0) {
line= line.substring(0, idx);
}
idx= line.indexOf(':');
if (idx < 0) {
continue;
}
final String descr= new String(line.substring(idx + 1).trim());
line= line.substring(0, idx);
this.index.addDefaultKeyword(line.split("\\|"), descr); //$NON-NLS-1$
}
m.addWorked(10);
return;
}
catch (final StatusException e) {
if (e.getStatus().getSeverity() == Status.CANCEL) {
throw e;
}
errorCause= e;
}
catch (final Exception e) {
errorCause= e;
}
finally {
if (DEBUG) {
this.tempBuilder1.insert(0, "Read KEYWORDS.db file:\n<FILE>\n");
this.tempBuilder1.append("</FILE>\n");
this.index.log(new InfoStatus(BUNDLE_ID, this.tempBuilder1.toString()));
}
}
this.index.log(new ErrorStatus(BUNDLE_ID,
"An error occurred when loading the keyword list.",
errorCause ));
}
private void loadPackages(final RService r, final ProgressMonitor m) throws StatusException {
m.beginTask("Loading R package help.", 8 + 1);
Exception errorCause= null;
IndexJob job= null;
try {
final RPkgManagerDataset rPkgDataset= this.rPkgManager.getDataset(RPkgManager.INSTALLED,
r, m );
job= scheduleIndexJob(
String.format("Update R help index for '%1$s'", this.rEnvConfig.getName()));
final RuntimeRLibPaths rLibPaths= (RuntimeRLibPaths) rPkgDataset.getRLibPaths();
final RPkgCompilation<? extends RPkgBuilt> installed= rPkgDataset.getInstalled();
final ProgressMonitor mPkgs= m.newSubMonitor(8);
final List<String> names= installed.getNames();
for (int i= 0; i < names.size(); i++) {
mPkgs.setWorkRemaining(2 * (names.size() - i));
final RPkgBuilt pkgInfo= installed.getFirst(names.get(i));
if (this.index.checkPackage(pkgInfo.getName(),
pkgInfo.getVersion(), pkgInfo.getBuilt(),
pkgInfo.getLibLocation() )) {
continue;
}
mPkgs.checkCanceled();
mPkgs.beginSubTask(String.format("Loading data for package '%1$s'...", pkgInfo.getName()));
try {
final RLibLocationInfo libLocationInfo= rLibPaths.getInfo(pkgInfo.getLibLocation());
if (libLocationInfo == null) {
throw new StatusException(new ErrorStatus(BUNDLE_ID,
String.format("Failed to resolve library location '%1$s'.",
pkgInfo.getLibLocation() )));
}
final PkgTask task= new PkgTask(pkgInfo.getName(), pkgInfo.getVersion(), pkgInfo.getBuilt(),
pkgInfo.getLibLocation(), libLocationInfo );
{ final FunctionCall call= r.createFunctionCall(PKG_DESCR_FNAME);
call.addChar("lib", libLocationInfo.getDirectoryRPath()); //$NON-NLS-1$
call.addChar("name", pkgInfo.getName()); //$NON-NLS-1$
task.rDescr= RDataUtils.checkRCharVector(call.evalData(mPkgs.newSubMonitor(1)));
}
{ final FunctionCall call= r.createFunctionCall(PKG_RD_FNAME);
call.addChar("lib", libLocationInfo.getDirectoryRPath()); //$NON-NLS-1$
call.addChar("name", pkgInfo.getName()); //$NON-NLS-1$
task.rRd= RDataUtils.checkRList(call.evalData(mPkgs.newSubMonitor(1)));
}
job.add(task, mPkgs);
}
catch (final StatusException e) { // only core exceptions!
if (e.getStatus().getSeverity() == Status.CANCEL) {
throw e;
}
this.index.log(new ErrorStatus(BUNDLE_ID,
String.format("An error occurred when loading data for package '%1$s' in '%2$s'.",
pkgInfo.getName(), pkgInfo.getLibLocation() ),
e ));
}
}
m.beginSubTask("Finishing index of help...");
job.finish(m.newSubMonitor(2));
job= null;
return;
}
catch (final StatusException e) {
if (e.getStatus().getSeverity() == Status.CANCEL) {
throw e;
}
errorCause= e;
}
catch (final Exception e) {
errorCause= e;
}
finally {
if (job != null) {
job.cancel(m);
}
}
throw new StatusException(new ErrorStatus(BUNDLE_ID,
"An error occurred when loading the package data.",
errorCause ));
}
protected abstract IndexJob scheduleIndexJob(final String name);
private static final Pattern URL_SPLIT_PATTERN= Pattern.compile("(?:,|\\s)+"); //$NON-NLS-1$
private RPkgDescription createDescription(final PkgTask task) throws Exception {
final RCharacterStore data= RDataUtils.checkLengthEqual(task.rDescr.getData(), PKG_DESCR_LENGTH);
final RNumVersion version;
{ final String versionString= RDataUtils.checkValue(data, PKG_DESCR_IDX_VERSION);
final RNumVersion taskVersion= task.version;
if (taskVersion != null) {
if (!taskVersion.toString().equals(versionString)) {
throw new Exception(
String.format("Unexpected package version: expected=%1$s, found=%2$s",
taskVersion, versionString ));
}
version= taskVersion;
}
else {
version= RNumVersion.create(versionString);
}
}
final String built;
{ final String builtString= RDataUtils.checkValue(data, PKG_DESCR_IDX_BUILT);
final String taskBuilt= task.built;
if (taskBuilt != null) {
if (!taskBuilt.equals(builtString)) {
throw new Exception(
String.format("Unexpected package built: expected=%1$s, found=%2$s",
taskBuilt, builtString ));
}
built= taskBuilt;
}
else {
built= builtString;
}
}
final ImList<String> urls;
{ String urlString= data.get(PKG_DESCR_IDX_URL);
if (urlString != null && !(urlString= urlString.strip()).isEmpty()) {
urls= ImCollections.newList(URL_SPLIT_PATTERN.split(urlString));
}
else {
urls= ImCollections.emptyList();
}
}
return new BasicRPkgDescription(task.name,
version,
RDataUtils.getValue(data, PKG_DESCR_IDX_TITLE, ""), //$NON-NLS-1$
RDataUtils.getValue(data, PKG_DESCR_IDX_DESCRIPTION, ""), //$NON-NLS-1$
data.get(PKG_DESCR_IDX_AUTHOR),
data.get(PKG_DESCR_IDX_MAINTAINER),
urls,
built, task.libLocation );
}
private void processRdData(final String pkgName, final RList pkgList) throws Exception {
for (int j= 0; j < pkgList.getLength(); j++) {
final RObject rdObj= pkgList.get(j);
if (rdObj.getRClassName().equals("RdData")) { //$NON-NLS-1$
final RList rdData= (RList) rdObj;
final RdItem rdItem= new RdItem(pkgName, pkgList.getName(j));
{ final RStore<?> store= rdData.get("title").getData(); //$NON-NLS-1$
if (!store.isNA(0)) {
rdItem.setTitle(store.getChar(0));
}
}
{ final RStore<?> store= rdData.get("topics").getData(); //$NON-NLS-1$
for (int k= 0; k < store.getLength(); k++) {
if (!store.isNA(k)) {
final String alias= store.getChar(k).trim();
if (alias.length() > 0) {
rdItem.addTopic(alias);
}
}
}
}
{ final RStore<?> store= rdData.get("keywords").getData(); //$NON-NLS-1$
for (int k= 0; k < store.getLength(); k++) {
if (!store.isNA(k)) {
final String keyword= store.getChar(k).trim();
if (keyword.length() > 0) {
rdItem.addKeyword(keyword);
}
}
}
}
{ final RStore<?> store= rdData.get("concepts").getData(); //$NON-NLS-1$
for (int k= 0; k < store.getLength(); k++) {
if (!store.isNA(k)) {
final String concept= store.getChar(k).trim();
if (concept.length() > 0) {
rdItem.addConcept(concept);
}
}
}
}
{ final RStore<?> store= rdData.get("HTML").getData(); //$NON-NLS-1$
if (store != null && store.getStoreType() == RStore.CHARACTER) {
rdItem.setHtml(processHtml((RCharacterStore) store));
}
}
this.index.add(rdItem);
}
}
}
@SuppressWarnings("nls")
private String processHtml(final RCharacterStore store) {
this.tempBuilder1.setLength(0);
this.tempBuilder2.setLength(0);
int length= 0;
for (int i= 0; i < store.getLength(); i++) {
if (!store.isNA(i)) {
length+= store.getChar(i).length() + 2;
}
}
length+= 300;
this.tempBuilder2.ensureCapacity(length);
int topIndex= -1;
boolean inExamples= false;
this.tempBuilder2.append("<div class=\"toc\"><ul>");
for (int i= 0; i < store.getLength(); i++) {
Matcher matcher;
if (!store.isNA(i)) {
String line= store.getChar(i);
if (topIndex == -1) {
if (line.startsWith("<table ")) {
this.tempBuilder1.append("<table class=\"header\" ");
line= line.substring(7);
}
else if (line.startsWith("<h2>")) {
topIndex= this.tempBuilder1.length();
this.tempBuilder1.append("<h2 id=\"top\">");
line= line.substring(4);
}
}
else if (topIndex >= 0 && line.length() > 10) {
if (line.startsWith("<h3>")) {
if (inExamples) {
this.tempBuilder1.append(RHelpWebapp.HTML_END_EXAMPLES);
inExamples= false;
}
switch (line.charAt(4) - line.charAt(6)) {
case ('D' - 's'):
if (line.equals("<h3>Description</h3>")) {
this.tempBuilder2.append("<li><a href=\"#description\"><span class=\"mnemonic\">D</span>escription</a></li>"); //$NON-NLS-1$
line= "<h3 id=\"description\">Description</h3>";
break;
}
break;
case ('U' - 'a'):
if (line.equals("<h3>Usage</h3>")) {
this.tempBuilder2.append("<li><a href=\"#usage\"><span class=\"mnemonic\">U</span>sage</a></li>"); //$NON-NLS-1$
line= "<h3 id=\"usage\">Usage</h3>";
break;
}
break;
case ('A' - 'g'):
if (line.equals("<h3>Arguments</h3>")) {
this.tempBuilder2.append("<li><a href=\"#arguments\"><span class=\"mnemonic\">A</span>rguments</a></li>"); //$NON-NLS-1$
line= "<h3 id=\"arguments\">Arguments</h3>";
break;
}
break;
case ('D' - 't'):
if (line.equals("<h3>Details</h3>")) {
this.tempBuilder2.append("<li><a href=\"#details\">Deta<span class=\"mnemonic\">i</span>ls</a></li>");
line= "<h3 id=\"details\">Details</h3>";
break;
}
break;
case ('V' - 'l'):
if (line.equals("<h3>Value</h3>")) {
this.tempBuilder2.append("<li><a href=\"#value\"><span class=\"mnemonic\">V</span>alue</a></li>");
line= "<h3 id=\"value\">Value</h3>";
break;
}
break;
case ('A' - 't'):
if (line.equals("<h3>Author(s)</h3>")) {
this.tempBuilder2.append("<li><a href=\"#authors\">Auth<span class=\"mnemonic\">o</span>r(s)</a></li>");
line= "<h3 id=\"authors\">Author(s)</h3>";
break;
}
break;
case ('R' - 'f'):
if (line.equals("<h3>References</h3>")) {
this.tempBuilder2.append("<li><a href=\"#references\"><span class=\"mnemonic\">R</span>eferences</a></li>");
line= "<h3 id=\"references\">References</h3>";
break;
}
break;
case ('E' - 'a'):
if (line.equals("<h3>Examples</h3>")) {
this.tempBuilder2.append("<li><a href=\"#examples\"><span class=\"mnemonic\">E</span>xamples</a></li>");
line= "<h3 id=\"examples\">Examples</h3>" + RHelpWebapp.HTML_BEGIN_EXAMPLES;
inExamples= true;
break;
}
break;
case ('S' - 'e'):
if (line.equals("<h3>See Also</h3>")) {
this.tempBuilder2.append("<li><a href=\"#seealso\"><span class=\"mnemonic\">S</span>ee Also</a></li>");
line= "<h3 id=\"seealso\">See Also</h3>";
break;
}
break;
}
}
else if (line.startsWith(HR_PREFIX)
&& (matcher= HR_LINE_PATTERN.matcher(line)).find() ) {
if (inExamples) {
this.tempBuilder1.append(RHelpWebapp.HTML_END_EXAMPLES);
inExamples= false;
}
// if (line.startsWith("<hr><div align=\"center\">[Package <em>")) {
// this.tempBuilder1.append("<hr/><div class=\"toc\"><ul><li><a href=\"#top\">Top</a></li></ul></div>");
// }
this.tempBuilder1.append("<hr/>");
line= line.substring(matcher.end() - matcher.start());
}
}
this.tempBuilder1.append(line);
this.tempBuilder1.append('\r');
this.tempBuilder1.append('\n');
}
}
if (topIndex >= 0) {
this.tempBuilder2.append("</ul></div>");
this.tempBuilder1.insert(topIndex, this.tempBuilder2);
}
return this.tempBuilder1.toString();
}
protected Path toLocalPath(final String rPath,
final RService r, final ProgressMonitor m) throws StatusException {
return Path.of(rPath);
}
}