blob: 86d4b609daa043dbd02d6e4596fc079f15bf416b [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.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.List;
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.jcommons.status.ErrorStatus;
import org.eclipse.statet.rhelp.core.DocResource;
import org.eclipse.statet.rhelp.core.REnvHelpConfiguration;
import org.eclipse.statet.rhelp.core.RHelpCore;
import org.eclipse.statet.rhelp.core.RHelpKeyword;
import org.eclipse.statet.rhelp.core.RHelpKeywordGroup;
import org.eclipse.statet.rhelp.core.RHelpPage;
import org.eclipse.statet.rhelp.core.RPkgHelp;
import org.eclipse.statet.rj.renv.core.BasicRLibLocation;
import org.eclipse.statet.rj.renv.core.BasicRPkgDescription;
import org.eclipse.statet.rj.renv.core.REnvConfiguration;
import org.eclipse.statet.rj.renv.core.RLibLocation;
import org.eclipse.statet.rj.renv.core.RNumVersion;
import org.eclipse.statet.rj.renv.core.RPkgDescription;
@NonNullByDefault
public class SerUtil {
private static final int VERSION_11= 11;
private static final int VERSION_10= 10;
private static final int VERSION= VERSION_11;
public static final String READABLE_SERVER= "" + VERSION_11 + ',' + VERSION_10; //$NON-NLS-1$
private static final int COMPRESS= 1 << 4;
private boolean canRead(final int version, final REnvHelpConfiguration rEnvConfig) {
switch (version) {
case VERSION_11:
return true;
case VERSION_10:
return (rEnvConfig.getStateSharedType() == REnvConfiguration.SHARED_SERVER);
default:
return false;
}
}
private static final String RHELP_SER_FILE= "rhelp.ser"; //$NON-NLS-1$
private static class UnsupportedVersionException extends IOException {
private static final long serialVersionUID= 1L;
public UnsupportedVersionException(final int inputVersion) {
super("input= " + inputVersion);
}
}
public static interface Controller {
Object getFileLock();
boolean shouldSave();
void onSaved();
}
public static @Nullable Path getIndexDirectoryPath(final REnvHelpConfiguration rEnvConfig) {
try {
final Path stateDirectory= rEnvConfig.getStateSharedDirectoryPath();
if (stateDirectory != null) {
return stateDirectory.resolve("index"); //$NON-NLS-1$
}
}
catch (final Exception e) {}
return null;
}
public static Path getIndexDirectoryChecked(final REnvHelpConfiguration rEnvConfig) {
Exception e1= null;
try {
final Path stateDirectory= rEnvConfig.getStateSharedDirectoryPath();
if (stateDirectory != null) {
final Path indexDirectory= stateDirectory.resolve("index"); //$NON-NLS-1$
if (!Files.isDirectory(indexDirectory)) {
Files.createDirectories(indexDirectory);
}
return indexDirectory;
}
}
catch (final Exception e) {
e1= e;
}
throw new RuntimeException(
String.format("Index directory could not be resolved: '%1$s'.",
rEnvConfig.getStateSharedDirectory() ),
e1 );
}
public static Path getBasicDataFilePath(final Path indexDirectory) {
return indexDirectory.resolve(RHELP_SER_FILE);
}
public static @Nullable Path getBasicDataFilePath(final REnvHelpConfiguration rEnvConfig) {
final Path directory= getIndexDirectoryPath(rEnvConfig);
if (directory != null) {
return getBasicDataFilePath(directory);
}
return null;
}
public SerUtil() {
}
public boolean save(final REnvHelpConfiguration rEnvConfig, final REnvHelpImpl help,
final Controller controller) {
try {
final Path directory= getIndexDirectoryChecked(rEnvConfig);
final Path newFile= directory.resolve(RHELP_SER_FILE + ".new"); //$NON-NLS-1$
Files.deleteIfExists(newFile);
try (final FIO fio= FIO.get(new BufferedOutputStream(
Files.newOutputStream(newFile, StandardOpenOption.CREATE_NEW) ))) {
save(help, fio, COMPRESS);
fio.flush();
}
synchronized (controller.getFileLock()) {
if (!controller.shouldSave()) {
return false;
}
final Path serFile= directory.resolve(RHELP_SER_FILE);
Files.move(newFile, serFile, StandardCopyOption.REPLACE_EXISTING);
controller.onSaved();
return true;
}
}
catch (final Exception e) {
RHelpCoreInternals.log(new ErrorStatus(RHelpCore.BUNDLE_ID,
String.format("An error occurred when saving R help data for '%1$s'.",
rEnvConfig.getName() ),
e ));
return false;
}
}
public boolean save(final REnvHelpConfiguration rEnvConfig, final InputStream in,
final Controller controller) {
try {
final Path directory= getIndexDirectoryChecked(rEnvConfig);
final Path newFile= directory.resolve(RHELP_SER_FILE + ".new"); //$NON-NLS-1$
Files.deleteIfExists(newFile);
try (final OutputStream out= new BufferedOutputStream(
Files.newOutputStream(newFile, StandardOpenOption.CREATE_NEW) )) {
FIO.copy(in, out);
}
synchronized (controller.getFileLock()) {
if (!controller.shouldSave()) {
return false;
}
final Path serFile= directory.resolve(RHELP_SER_FILE);
Files.move(newFile, serFile, StandardCopyOption.REPLACE_EXISTING);
controller.onSaved();
return true;
}
}
catch (final Exception e) {
RHelpCoreInternals.log(new ErrorStatus(RHelpCore.BUNDLE_ID,
String.format("An error occurred when saving R help data for '%1$s'.",
rEnvConfig.getName() ),
e ));
return false;
}
}
public long getStamp(final REnvHelpConfiguration rEnvConfig) {
try {
final Path serFile= getBasicDataFilePath(rEnvConfig);
if (!Files.isRegularFile(serFile)) {
return REnvHelpImpl.NOT_AVAILABLE_STAMP;
}
try (final FIO fio= FIO.get(new BufferedInputStream(
Files.newInputStream(serFile), 64 ))) {
final int version= fio.readInt();
if (version != VERSION && !canRead(version, rEnvConfig)) {
return REnvHelpImpl.NOT_AVAILABLE_STAMP;
}
return fio.readLong();
}
}
catch (final Throwable e) {
if (e instanceof Error && !(e instanceof UnsupportedVersionException)) {
throw (Error) e;
}
RHelpCoreInternals.log(new ErrorStatus(RHelpCore.BUNDLE_ID,
String.format("An error occurred when loading R help data for '%1$s'.",
rEnvConfig.getName() ),
e ));
return REnvHelpImpl.NOT_AVAILABLE_STAMP;
}
}
public @Nullable REnvHelpImpl load(final REnvHelpConfiguration rEnvConfig) {
try {
final Path serFile= getBasicDataFilePath(rEnvConfig);
if (!Files.isRegularFile(serFile)) {
return null;
}
try (final FIO fio= FIO.get(new BufferedInputStream(
Files.newInputStream(serFile) ))) {
return load(rEnvConfig, fio);
}
}
catch (final Throwable e) {
if (e instanceof Error) {
throw (Error) e;
}
RHelpCoreInternals.log(new ErrorStatus(RHelpCore.BUNDLE_ID,
String.format("An error occurred when loading R help data for '%1$s'.",
rEnvConfig.getName() ),
e ));
return null;
}
}
public void save(final REnvHelpImpl help, final FIO fio, final int flags)
throws IOException {
fio.writeInt(VERSION);
fio.writeLong(help.getStamp());
fio.writeInt(flags);
if ((flags & COMPRESS) != 0) {
fio.enableCompression();
}
fio.writeString(help.getDocDir());
{ final ImList<DocResource> resources= help.getManuals();
final int count= resources.size();
fio.writeInt(count);
for (int i= 0; i < count; i++) {
saveDocResource(resources.get(i), fio);
}
}
{ final ImList<DocResource> resources= help.getMiscResources();
final int count= resources.size();
fio.writeInt(count);
for (int i= 0; i < count; i++) {
saveDocResource(resources.get(i), fio);
}
}
{ final List<RHelpKeywordGroup> keywordGroups= help.getKeywords();
final int count= keywordGroups.size();
fio.writeInt(count);
for (int i= 0; i < keywordGroups.size(); i++) {
saveKeywordGroup(keywordGroups.get(i), fio);
}
}
{ final List<RPkgHelp> packages= help.getPkgs();
final int count= packages.size();
fio.writeInt(count);
for (int i= 0; i < packages.size(); i++) {
savePackage(packages.get(i), fio);
}
}
}
public REnvHelpImpl load(final REnvHelpConfiguration rEnvConfig, final FIO fio)
throws IOException {
final int version= fio.readInt();
if (version != VERSION && !canRead(version, rEnvConfig)) {
throw new UnsupportedVersionException(version);
}
final long stamp= fio.readLong();
final int flags= fio.readInt();
if ((flags & COMPRESS) != 0) {
fio.enableCompression();
}
final String docDir= fio.readString();
final ImList<DocResource> manuals;
{ final int count= fio.readInt();
final DocResource[] array= new org.eclipse.statet.rhelp.core.DocResource[count];
for (int i= 0; i < count; i++) {
array[i]= readDocResource(fio);
}
manuals= ImCollections.newList(array);
}
final ImList<DocResource> miscRes;
{ final int count= fio.readInt();
final DocResource[] array= new @NonNull DocResource[count];
for (int i= 0; i < count; i++) {
array[i]= readDocResource(fio);
}
miscRes= ImCollections.newList(array);
}
final ImList<RHelpKeywordGroup> keywordGroups;
{ final int count= fio.readInt();
final RHelpKeywordGroup[] array= new @NonNull RHelpKeywordGroup[count];
for (int i= 0; i < count; i++) {
array[i]= loadKeywordGroup(fio);
}
keywordGroups= ImCollections.newList(array);
}
final ImList<RPkgHelp> pkgHelps;
{ final int count= fio.readInt();
final RPkgHelp[] array= new @NonNull RPkgHelp[count];
for (int i= 0; i < count; i++) {
array[i]= loadPackage(rEnvConfig, fio);
}
pkgHelps= ImCollections.newList(array);
}
return new REnvHelpImpl(rEnvConfig.getREnv(), stamp,
docDir, manuals, miscRes,
keywordGroups, pkgHelps );
}
private void saveDocResource(final DocResource res,
final FIO fio) throws IOException {
fio.writeString(res.getTitle());
fio.writeString(res.getPath());
fio.writeString(res.getPdfPath());
}
private DocResource readDocResource(final FIO fio) throws IOException {
return new DocResource(
fio.readNonNullString(),
fio.readNonNullString(),
fio.readString() );
}
private void saveKeywordGroup(final RHelpKeywordGroup group,
final FIO fio) throws IOException {
fio.writeString(group.getLabel());
fio.writeString(group.getDescription());
final List<RHelpKeyword> keywords= group.getNestedKeywords();
final int count= keywords.size();
fio.writeInt(count);
for (int i= 0; i < count; i++) {
saveKeyword(keywords.get(i), fio);
}
}
private RHelpKeywordGroup loadKeywordGroup(final FIO fio) throws IOException {
final String label= fio.readString();
final String description= fio.readString();
final int count= fio.readInt();
final RHelpKeyword[] keywords= new @NonNull RHelpKeyword[count];
for (int i= 0; i < count; i++) {
keywords[i]= loadKeyword(fio);
}
return new RHelpKeywordGroupImpl(label, description, ImCollections.newList(keywords));
}
private void saveKeyword(final RHelpKeyword keyword, final FIO fio)
throws IOException {
fio.writeString(keyword.getKeyword());
fio.writeString(keyword.getDescription());
final List<RHelpKeyword> nestedKeywords= keyword.getNestedKeywords();
final int count= nestedKeywords.size();
fio.writeInt(count);
for (int i= 0; i < nestedKeywords.size(); i++) {
saveKeyword(nestedKeywords.get(i), fio);
}
}
private RHelpKeyword loadKeyword(final FIO fio)
throws IOException {
final String keyword= fio.readString();
final String description= fio.readString();
final int n= fio.readInt();
final RHelpKeyword[] nestedKeywords= new @NonNull RHelpKeyword[n];
for (int i= 0; i < n; i++) {
nestedKeywords[i]= loadKeyword(fio);
}
return new RHelpKeywordImpl(keyword, description, ImCollections.newList(nestedKeywords));
}
private void savePackage(final RPkgHelp pkgHelp, final FIO fio)
throws IOException {
savePkgDescription(pkgHelp.getPkgDescription(), fio);
final List<RHelpPage> pages= pkgHelp.getPages();
final int nPages= pages.size();
fio.writeInt(nPages);
for (int i= 0; i < nPages; i++) {
savePage(pages.get(i), fio);
}
}
private RPkgHelp loadPackage(final REnvHelpConfiguration rEnvConfig, final FIO fio)
throws IOException {
final RPkgDescription pkgDescription= loadPkgDescription(rEnvConfig, fio);
final int nPages= fio.readInt();
final RHelpPage[] pages= new @NonNull RHelpPage[nPages];
final RPkgHelpImpl pkg= new RPkgHelpImpl(pkgDescription, rEnvConfig.getREnv());
for (int i= 0; i < nPages; i++) {
pages[i]= loadPage(pkg, fio);
}
pkg.setPages(ImCollections.newList(pages));
return pkg;
}
private void savePkgDescription(final RPkgDescription pkgDescription,
final FIO fio) throws IOException {
fio.writeString(pkgDescription.getName());
fio.writeString(pkgDescription.getVersion().toString());
fio.writeString(pkgDescription.getTitle());
fio.writeString(pkgDescription.getDescription());
fio.writeString(pkgDescription.getAuthor());
fio.writeString(pkgDescription.getMaintainer());
fio.writeString(pkgDescription.getUrl());
fio.writeString(pkgDescription.getBuilt());
fio.writeString(pkgDescription.getLibLocation().getDirectory());
}
private RPkgDescription loadPkgDescription(final REnvHelpConfiguration rEnvConfig,
final FIO fio) throws IOException {
final String name= fio.readNonNullString().intern();
final String version= fio.readNonNullString();
final String title= fio.readNonNullString();
final String description= fio.readNonNullString();
final String author= fio.readString();
final String maintainer= fio.readString();
final String url= fio.readString();
final String built= fio.readNonNullString();
final RLibLocation libLocation= getLibLocationSafe(rEnvConfig, fio.readNonNullString());
return new BasicRPkgDescription(name, RNumVersion.create(version),
title, description,
author, maintainer,
url,
built, libLocation);
}
private RLibLocation getLibLocationSafe(final REnvHelpConfiguration rEnvConfig, final String directory) {
RLibLocation libLocation= rEnvConfig.getRLibLocationByDirectory(directory);
if (libLocation == null) {
libLocation= new BasicRLibLocation(RLibLocation.MISSING, directory, null);
}
return libLocation;
}
private void savePage(final RHelpPage page, final FIO fio)
throws IOException {
fio.writeString(page.getName());
{ final ImList<String> topics= page.getTopics();
final int nTopics= topics.size();
fio.writeInt(nTopics);
for (int i= 0; i < nTopics; i++) {
final String topic= topics.get(i);
fio.writeString((topic == page.getName()) ? null : topic);
}
}
fio.writeString(page.getTitle());
}
private RHelpPage loadPage(final RPkgHelp pkg, final FIO fio)
throws IOException {
final String name= fio.readNonNullString().intern();
final ImList<String> topics;
{ final int nTopics= fio.readInt();
final String[] array= new @NonNull String[nTopics];
for (int i= 0; i < array.length; i++) {
final String topic= fio.readString();
array[i]= (topic == null) ? name : topic.intern();
}
topics= ImCollections.newList(array);
}
final String title= fio.readNonNullString();
return new RHelpPageImpl(pkg, name, topics, title);
}
}