blob: 7d56952e04727b0f849457b826bf8cbb3302911a [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2010, 2020 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.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 {
public static final int VERSION_12= 12;
public static final int VERSION_11= 11;
public static final int VERSION_10= 10;
public static final int CURRENT_VERSION= VERSION_12;
public static final int[] KNOWN_VERSIONS= {
VERSION_12,
VERSION_11,
VERSION_10 };
private static final int COMPRESS= 1 << 4;
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 canRead(final int version, final String type) {
switch (version) {
case VERSION_12:
case VERSION_11:
return true;
case VERSION_10:
return (type == REnvConfiguration.SHARED_SERVER);
default:
return false;
}
}
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 DataStream out= DataStream.get(new BufferedOutputStream(
Files.newOutputStream(newFile, StandardOpenOption.CREATE_NEW) ))) {
save(help, out, COMPRESS);
out.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.copy(in, newFile, StandardCopyOption.REPLACE_EXISTING);
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 DataStream in= DataStream.get(new BufferedInputStream(
Files.newInputStream(serFile), 64 ))) {
final int version= in.readVersion();
if (version != CURRENT_VERSION
&& !canRead(version, rEnvConfig.getStateSharedType())) {
return REnvHelpImpl.NOT_AVAILABLE_STAMP;
}
return in.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 DataStream in= DataStream.get(new BufferedInputStream(
Files.newInputStream(serFile) ))) {
return load(rEnvConfig, in);
}
}
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 DataStream out, final int flags)
throws IOException {
out.writeVersion(CURRENT_VERSION);
out.writeLong(help.getStamp());
out.writeInt(flags);
if ((flags & COMPRESS) != 0) {
out.enableCompression();
}
out.writeString(help.getDocDir());
{ final ImList<DocResource> resources= help.getManuals();
final int count= resources.size();
out.writeInt(count);
for (int i= 0; i < count; i++) {
saveDocResource(resources.get(i), out);
}
}
{ final ImList<DocResource> resources= help.getMiscResources();
final int count= resources.size();
out.writeInt(count);
for (int i= 0; i < count; i++) {
saveDocResource(resources.get(i), out);
}
}
{ final List<RHelpKeywordGroup> keywordGroups= help.getKeywords();
final int count= keywordGroups.size();
out.writeInt(count);
for (int i= 0; i < keywordGroups.size(); i++) {
saveKeywordGroup(keywordGroups.get(i), out);
}
}
{ final List<RPkgHelp> packages= help.getPkgs();
final int count= packages.size();
out.writeInt(count);
for (int i= 0; i < packages.size(); i++) {
savePackage(packages.get(i), out);
}
}
}
public REnvHelpImpl load(final REnvHelpConfiguration rEnvConfig, final DataStream in)
throws IOException {
final int version= in.readVersion();
if (version != CURRENT_VERSION
&& !canRead(version, rEnvConfig.getStateSharedType())) {
throw new UnsupportedVersionException(version);
}
final long stamp= in.readLong();
final int flags= in.readInt();
if ((flags & COMPRESS) != 0) {
in.enableCompression();
}
final String docDir= in.readString();
final ImList<DocResource> manuals;
{ final int count= in.readInt();
final DocResource[] array= new @NonNull DocResource[count];
for (int i= 0; i < count; i++) {
array[i]= readDocResource(in);
}
manuals= ImCollections.newList(array);
}
final ImList<DocResource> miscRes;
{ final int count= in.readInt();
final DocResource[] array= new @NonNull DocResource[count];
for (int i= 0; i < count; i++) {
array[i]= readDocResource(in);
}
miscRes= ImCollections.newList(array);
}
final ImList<RHelpKeywordGroup> keywordGroups;
{ final int count= in.readInt();
final RHelpKeywordGroup[] array= new @NonNull RHelpKeywordGroup[count];
for (int i= 0; i < count; i++) {
array[i]= loadKeywordGroup(in);
}
keywordGroups= ImCollections.newList(array);
}
final ImList<RPkgHelp> pkgHelps;
{ final int count= in.readInt();
final RPkgHelp[] array= new @NonNull RPkgHelp[count];
for (int i= 0; i < count; i++) {
array[i]= loadPackage(rEnvConfig, in);
}
pkgHelps= ImCollections.newList(array);
}
return new REnvHelpImpl(rEnvConfig.getREnv(), version, stamp,
docDir, manuals, miscRes,
keywordGroups, pkgHelps );
}
private void saveDocResource(final DocResource res,
final DataStream out) throws IOException {
out.writeString(res.getTitle());
out.writeString(res.getPath());
out.writeString(res.getPdfPath());
}
private DocResource readDocResource(final DataStream in) throws IOException {
return new DocResource(
in.readNonNullString(),
in.readNonNullString(),
in.readString() );
}
private void saveKeywordGroup(final RHelpKeywordGroup group,
final DataStream out) throws IOException {
out.writeString(group.getLabel());
out.writeString(group.getDescription());
final List<RHelpKeyword> keywords= group.getNestedKeywords();
final int count= keywords.size();
out.writeInt(count);
for (int i= 0; i < count; i++) {
saveKeyword(keywords.get(i), out);
}
}
private RHelpKeywordGroup loadKeywordGroup(final DataStream in) throws IOException {
final String label= in.readNonNullString();
final String description= in.readNonNullString();
final int count= in.readInt();
final RHelpKeyword[] keywords= new @NonNull RHelpKeyword[count];
for (int i= 0; i < count; i++) {
keywords[i]= loadKeyword(in);
}
return new RHelpKeywordGroupImpl(label, description, ImCollections.newList(keywords));
}
private void saveKeyword(final RHelpKeyword keyword, final DataStream out)
throws IOException {
out.writeString(keyword.getKeyword());
out.writeString(keyword.getDescription());
final List<RHelpKeyword> nestedKeywords= keyword.getNestedKeywords();
final int count= nestedKeywords.size();
out.writeInt(count);
for (int i= 0; i < nestedKeywords.size(); i++) {
saveKeyword(nestedKeywords.get(i), out);
}
}
private RHelpKeyword loadKeyword(final DataStream in)
throws IOException {
final String keyword= in.readNonNullString();
final String description= in.readNonNullString();
final int n= in.readInt();
final RHelpKeyword[] nestedKeywords= new @NonNull RHelpKeyword[n];
for (int i= 0; i < n; i++) {
nestedKeywords[i]= loadKeyword(in);
}
return new RHelpKeywordImpl(keyword, description, ImCollections.newList(nestedKeywords));
}
private void savePackage(final RPkgHelp pkgHelp, final DataStream out)
throws IOException {
savePkgDescription(pkgHelp.getPkgDescription(), out);
final List<RHelpPage> pages= pkgHelp.getPages();
final int nPages= pages.size();
out.writeInt(nPages);
for (int i= 0; i < nPages; i++) {
savePage(pages.get(i), out);
}
}
private RPkgHelp loadPackage(final REnvHelpConfiguration rEnvConfig, final DataStream in)
throws IOException {
final RPkgDescription pkgDescription= loadPkgDescription(rEnvConfig, in);
final int nPages= in.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, in);
}
pkg.setPages(ImCollections.newList(pages));
return pkg;
}
private void savePkgDescription(final RPkgDescription pkgDescription,
final DataStream out) throws IOException {
out.writeString(pkgDescription.getName());
out.writeString(pkgDescription.getVersion().toString());
out.writeString(pkgDescription.getTitle());
out.writeString(pkgDescription.getDescription());
out.writeString(pkgDescription.getAuthor());
out.writeString(pkgDescription.getMaintainer());
out.writeString(pkgDescription.getUrl());
out.writeString(pkgDescription.getBuilt());
out.writeString(pkgDescription.getLibLocation().getDirectory());
}
private RPkgDescription loadPkgDescription(final REnvHelpConfiguration rEnvConfig,
final DataStream in) throws IOException {
final String name= in.readNonNullString().intern();
final String version= in.readNonNullString();
final String title= in.readNonNullString();
final String description= in.readNonNullString();
final String author= in.readString();
final String maintainer= in.readString();
final String url= in.readString();
final String built= in.readNonNullString();
final RLibLocation libLocation= getLibLocationSafe(rEnvConfig, in.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 DataStream out)
throws IOException {
if (out.getVersion() >= VERSION_12) {
out.writeByte(RHelpPageImpl.createFlags(page.isInternal()));
}
out.writeString(page.getName());
{ final ImList<String> topics= page.getTopics();
final int nTopics= topics.size();
out.writeInt(nTopics);
for (int i= 0; i < nTopics; i++) {
final String topic= topics.get(i);
out.writeString((topic == page.getName()) ? null : topic);
}
}
out.writeString(page.getTitle());
}
private RHelpPage loadPage(final RPkgHelp pkg, final DataStream in)
throws IOException {
final byte pageFlags= (in.getVersion() >= VERSION_12) ?
in.readByte() :
0;
final String name= in.readNonNullString().intern();
final ImList<String> topics;
{ final int nTopics= in.readInt();
final String[] array= new @NonNull String[nTopics];
for (int i= 0; i < array.length; i++) {
final String topic= in.readString();
array[i]= (topic == null) ? name : topic.intern();
}
topics= ImCollections.newList(array);
}
final String title= in.readNonNullString();
return new RHelpPageImpl(pkg, name, pageFlags, topics, title);
}
}