| /*=============================================================================# |
| # 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); |
| } |
| |
| } |