blob: 1864347700987db5eae8af8c42e2db9fa80da637 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2016 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.core.internal.registry;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.Map.Entry;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.spi.RegistryContributor;
public class TableWriter {
private static final byte fileError = 0;
File mainDataFile;
File extraDataFile;
File tableFile;
File contributionsFile;
File contributorsFile;
File namespacesFile;
File orphansFile;
void setMainDataFile(File main) {
mainDataFile = main;
}
void setExtraDataFile(File extra) {
extraDataFile = extra;
}
void setTableFile(File table) {
tableFile = table;
}
void setContributionsFile(File fileName) {
contributionsFile = fileName;
}
void setContributorsFile(File fileName) {
contributorsFile = fileName;
}
void setNamespacesFile(File fileName) {
namespacesFile = fileName;
}
void setOrphansFile(File orphan) {
orphansFile = orphan;
}
DataOutputStream mainOutput;
DataOutputStream extraOutput;
FileOutputStream mainFileOutput = null;
FileOutputStream extraFileOutput = null;
private OffsetTable offsets;
private final ExtensionRegistry registry;
private RegistryObjectManager objectManager;
public TableWriter(ExtensionRegistry registry) {
this.registry = registry;
}
private int getExtraDataPosition() {
return extraOutput.size();
}
public boolean saveCache(RegistryObjectManager objectManager, long timestamp) {
this.objectManager = objectManager;
try {
if (!openFiles())
return false;
try {
saveExtensionRegistry(timestamp);
} catch (IOException io) {
log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, RegistryMessages.meta_registryCacheWriteProblems, io));
return false;
}
} finally {
closeFiles();
}
return true;
}
private boolean openFiles() {
try {
mainFileOutput = new FileOutputStream(mainDataFile);
mainOutput = new DataOutputStream(new BufferedOutputStream(mainFileOutput));
extraFileOutput = new FileOutputStream(extraDataFile);
extraOutput = new DataOutputStream(new BufferedOutputStream(extraFileOutput));
} catch (FileNotFoundException e) {
if (mainFileOutput != null)
try {
mainFileOutput.close();
} catch (IOException e1) {
//Ignore
}
log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, RegistryMessages.meta_unableToCreateCache, e));
return false;
}
return true;
}
private void closeFiles() {
try {
if (mainOutput != null) {
mainOutput.flush();
if (mainFileOutput.getFD().valid()) {
mainFileOutput.getFD().sync();
}
mainOutput.close();
}
} catch (IOException e) {
log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, RegistryMessages.meta_registryCacheWriteProblems, e));
e.printStackTrace();
}
try {
if (extraOutput != null) {
extraOutput.flush();
if (extraFileOutput.getFD().valid()) {
extraFileOutput.getFD().sync();
}
extraOutput.close();
}
} catch (IOException e) {
log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, RegistryMessages.meta_registryCacheWriteProblems, e));
e.printStackTrace();
}
}
private void saveExtensionRegistry(long timestamp) throws IOException {
ExtensionPointHandle[] points = objectManager.getExtensionPointsHandles();
offsets = new OffsetTable(objectManager.getNextId());
for (ExtensionPointHandle point : points) {
saveExtensionPoint(point);
}
saveOrphans();
saveContributions(objectManager.getContributions());
saveContributors(objectManager.getContributors());
saveNamespaces(objectManager.getNamespacesIndex());
closeFiles(); //Close the files here so we can write the appropriate size information in the table file.
saveTables(timestamp); //Write the table last so if that is something went wrong we can know
}
private void saveContributions(KeyedHashSet[] contributions) throws IOException {
FileOutputStream fosNamespace = new FileOutputStream(contributionsFile);
DataOutputStream outputNamespace = new DataOutputStream(new BufferedOutputStream(fosNamespace));
KeyedElement[] newElements = contributions[0].elements();
KeyedElement[] formerElements = contributions[1].elements();
// get count of contributions that will be cached
int cacheSize = 0;
for (KeyedElement newElement : newElements) {
if (((Contribution) newElement).shouldPersist()) {
cacheSize++;
}
}
for (KeyedElement formerElement : formerElements) {
if (((Contribution) formerElement).shouldPersist()) {
cacheSize++;
}
}
outputNamespace.writeInt(cacheSize);
for (KeyedElement newElement : newElements) {
Contribution element = (Contribution) newElement;
if (element.shouldPersist()) {
writeStringOrNull(element.getContributorId(), outputNamespace);
saveArray(filterContributionChildren(element), outputNamespace);
}
}
for (KeyedElement formerElement : formerElements) {
Contribution element = (Contribution) formerElement;
if (element.shouldPersist()) {
writeStringOrNull(element.getContributorId(), outputNamespace);
saveArray(filterContributionChildren(element), outputNamespace);
}
}
outputNamespace.flush();
fosNamespace.getFD().sync();
outputNamespace.close();
}
// Contribution has raw children in a unique format that combines extensions and extension points.
// To filter, need to dis-assmeble, filter, and then re-assemble its raw children
private int[] filterContributionChildren(Contribution element) {
int[] extensionPoints = filter(element.getExtensionPoints());
int[] extensions = filter(element.getExtensions());
int[] filteredRawChildren = new int[2 + extensionPoints.length + extensions.length];
System.arraycopy(extensionPoints, 0, filteredRawChildren, 2, extensionPoints.length);
System.arraycopy(extensions, 0, filteredRawChildren, 2 + extensionPoints.length, extensions.length);
filteredRawChildren[Contribution.EXTENSION_POINT] = extensionPoints.length;
filteredRawChildren[Contribution.EXTENSION] = extensions.length;
return filteredRawChildren;
}
private void saveNamespaces(KeyedHashSet namespacesIndex) throws IOException {
FileOutputStream fosNamespace = new FileOutputStream(namespacesFile);
DataOutputStream outputNamespace = new DataOutputStream(new BufferedOutputStream(fosNamespace));
KeyedElement[] elements = namespacesIndex.elements();
KeyedElement[] cachedElements = new KeyedElement[elements.length];
int cacheSize = 0;
for (KeyedElement e : elements) {
RegistryIndexElement element = (RegistryIndexElement) e;
int[] extensionPoints = filter(element.getExtensionPoints());
int[] extensions = filter(element.getExtensions());
if (extensionPoints.length == 0 && extensions.length == 0)
continue;
RegistryIndexElement cachedElement = new RegistryIndexElement((String) element.getKey(), extensionPoints, extensions);
cachedElements[cacheSize] = cachedElement;
cacheSize++;
}
outputNamespace.writeInt(cacheSize);
for (int i = 0; i < cacheSize; i++) {
RegistryIndexElement element = (RegistryIndexElement) cachedElements[i];
writeStringOrNull((String) element.getKey(), outputNamespace);
saveArray(element.getExtensionPoints(), outputNamespace); // it was pre-filtered as we counted the number of elements
saveArray(element.getExtensions(), outputNamespace); // it was pre-filtered as we counted the number of elements
}
outputNamespace.flush();
fosNamespace.getFD().sync();
outputNamespace.close();
}
private void saveContributors(HashMap<?, ?> contributors) throws IOException {
FileOutputStream fosContributors = new FileOutputStream(contributorsFile);
DataOutputStream outputContributors = new DataOutputStream(new BufferedOutputStream(fosContributors));
Collection<?> entries = contributors.values();
outputContributors.writeInt(entries.size());
for (Object entry : entries) {
RegistryContributor contributor = (RegistryContributor) entry;
writeStringOrNull(contributor.getActualId(), outputContributors);
writeStringOrNull(contributor.getActualName(), outputContributors);
writeStringOrNull(contributor.getId(), outputContributors);
writeStringOrNull(contributor.getName(), outputContributors);
}
outputContributors.flush();
fosContributors.getFD().sync();
outputContributors.close();
}
private void saveTables(long registryTimeStamp) throws IOException {
FileOutputStream fosTable = new FileOutputStream(tableFile);
DataOutputStream outputTable = new DataOutputStream(new BufferedOutputStream(fosTable));
writeCacheHeader(outputTable, registryTimeStamp);
outputTable.writeInt(objectManager.getNextId());
offsets.save(outputTable);
objectManager.getExtensionPoints().save(outputTable, objectManager); // uses writer to filter contents
outputTable.flush();
fosTable.getFD().sync();
outputTable.close();
}
private void writeCacheHeader(DataOutputStream output, long registryTimeStamp) throws IOException {
output.writeInt(TableReader.CACHE_VERSION);
output.writeLong(registry.computeState());
output.writeLong(registryTimeStamp);
output.writeLong(mainDataFile.length());
output.writeLong(extraDataFile.length());
output.writeLong(contributionsFile.length());
output.writeLong(contributorsFile.length());
output.writeLong(namespacesFile.length());
output.writeLong(orphansFile.length());
output.writeUTF(RegistryProperties.getProperty(IRegistryConstants.PROP_OS, RegistryProperties.empty));
output.writeUTF(RegistryProperties.getProperty(IRegistryConstants.PROP_WS, RegistryProperties.empty));
output.writeUTF(RegistryProperties.getProperty(IRegistryConstants.PROP_NL, RegistryProperties.empty));
output.writeBoolean(registry.isMultiLanguage());
}
private void saveArray(int[] array, DataOutputStream out) throws IOException {
if (array == null) {
out.writeInt(0);
return;
}
out.writeInt(array.length);
for (int element : array) {
out.writeInt(element);
}
}
private void saveExtensionPoint(ExtensionPointHandle xpt) throws IOException {
if (!xpt.shouldPersist())
return;
//save the file position
offsets.put(xpt.getId(), mainOutput.size());
//save the extensionPoint
mainOutput.writeInt(xpt.getId());
saveArray(filter(xpt.getObject().getRawChildren()), mainOutput);
mainOutput.writeInt(getExtraDataPosition());
saveExtensionPointData(xpt);
saveExtensions(xpt.getExtensions(), mainOutput);
}
private void saveExtension(ExtensionHandle ext, DataOutputStream outputStream) throws IOException {
if (!ext.shouldPersist())
return;
offsets.put(ext.getId(), outputStream.size());
outputStream.writeInt(ext.getId());
writeStringOrNull(ext.getSimpleIdentifier(), outputStream);
writeStringOrNull(ext.getNamespaceIdentifier(), outputStream);
saveArray(filter(ext.getObject().getRawChildren()), outputStream);
outputStream.writeInt(getExtraDataPosition());
saveExtensionData(ext);
}
private void writeStringArray(String[] array, DataOutputStream outputStream) throws IOException {
outputStream.writeInt(array == null ? 0 : array.length);
for (int i = 0; i < (array == null ? 0 : array.length); i++) {
writeStringOrNull(array[i], outputStream);
}
}
private void writeStringArray(String[] array, int size, DataOutputStream outputStream) throws IOException {
outputStream.writeInt(array == null ? 0 : size);
if (array == null)
return;
for (int i = 0; i < size; i++) {
writeStringOrNull(array[i], outputStream);
}
}
//Save Configuration elements depth first
private void saveConfigurationElement(ConfigurationElementHandle element, DataOutputStream outputStream, DataOutputStream extraOutputStream, int depth) throws IOException {
if (!element.shouldPersist())
return;
DataOutputStream currentOutput = outputStream;
if (depth > 2)
currentOutput = extraOutputStream;
offsets.put(element.getId(), currentOutput.size());
currentOutput.writeInt(element.getId());
ConfigurationElement actualCe = (ConfigurationElement) element.getObject();
writeStringOrNull(actualCe.getContributorId(), currentOutput);
writeStringOrNull(actualCe.getName(), currentOutput);
currentOutput.writeInt(actualCe.parentId);
currentOutput.writeByte(actualCe.parentType);
currentOutput.writeInt(depth > 1 ? extraOutputStream.size() : -1);
writeStringArray(actualCe.getPropertiesAndValue(), currentOutput);
//save the children
saveArray(filter(actualCe.getRawChildren()), currentOutput);
if (actualCe instanceof ConfigurationElementMulti) {
ConfigurationElementMulti multiCE = (ConfigurationElementMulti) actualCe;
int NLs = multiCE.getNumCachedLocales();
currentOutput.writeInt(NLs);
if (NLs != 0) {
writeStringArray(multiCE.getCachedLocales(), NLs, currentOutput);
String[][] translated = multiCE.getCachedTranslations();
for (int i = 0; i < NLs; i++) {
writeStringArray(translated[i], currentOutput);
}
}
}
ConfigurationElementHandle[] childrenCEs = (ConfigurationElementHandle[]) element.getChildren();
for (ConfigurationElementHandle childrenCE : childrenCEs) {
saveConfigurationElement(childrenCE, outputStream, extraOutputStream, depth + 1);
}
}
private void saveExtensions(IExtension[] exts, DataOutputStream outputStream) throws IOException {
for (IExtension ext : exts) {
saveExtension((ExtensionHandle) ext, outputStream);
}
for (IExtension ext : exts) {
if (!((ExtensionHandle) ext).shouldPersist()) {
continue;
}
IConfigurationElement[] ces = ext.getConfigurationElements();
int countCElements = 0;
boolean[] save = new boolean[ces.length];
for (int j = 0; j < ces.length; j++) {
if (((ConfigurationElementHandle) ces[j]).shouldPersist()) {
save[j] = true;
countCElements++;
} else
save[j] = false;
}
outputStream.writeInt(countCElements);
for (int j = 0; j < ces.length; j++) {
if (save[j])
saveConfigurationElement((ConfigurationElementHandle) ces[j], outputStream, extraOutput, 1);
}
}
}
private void saveExtensionPointData(ExtensionPointHandle xpt) throws IOException {
writeStringOrNull(xpt.getLabelAsIs(), extraOutput);
writeStringOrNull(xpt.getSchemaReference(), extraOutput);
writeStringOrNull(xpt.getUniqueIdentifier(), extraOutput);
writeStringOrNull(xpt.getNamespaceIdentifier(), extraOutput);
writeStringOrNull(((ExtensionPoint) xpt.getObject()).getContributorId(), extraOutput);
}
private void saveExtensionData(ExtensionHandle extension) throws IOException {
writeStringOrNull(extension.getLabelAsIs(), extraOutput);
writeStringOrNull(extension.getExtensionPointUniqueIdentifier(), extraOutput);
writeStringOrNull(extension.getContributorId(), extraOutput);
}
private void writeStringOrNull(String string, DataOutputStream out) throws IOException {
if (string == null)
out.writeByte(TableReader.NULL);
else {
byte[] data = string.getBytes(StandardCharsets.UTF_8);
if (data.length > 65535) {
out.writeByte(TableReader.LOBJECT);
out.writeInt(data.length);
out.write(data);
} else {
out.writeByte(TableReader.OBJECT);
out.writeUTF(string);
}
}
}
private void saveOrphans() throws IOException {
Map<String, int[]> orphans = objectManager.getOrphanExtensions();
Map<String, int[]> filteredOrphans = new HashMap<>();
for (Entry<String, int[]> entry : orphans.entrySet()) {
int[] filteredValue = filter(entry.getValue());
if (filteredValue.length != 0)
filteredOrphans.put(entry.getKey(), filteredValue);
}
FileOutputStream fosOrphan = new FileOutputStream(orphansFile);
DataOutputStream outputOrphan = new DataOutputStream(new BufferedOutputStream(fosOrphan));
outputOrphan.writeInt(filteredOrphans.size());
Set<Entry<String, int[]>> elements = filteredOrphans.entrySet();
for (Entry<String, int[]> entry : elements) {
outputOrphan.writeUTF(entry.getKey());
saveArray(entry.getValue(), outputOrphan);
}
for (Entry<String, int[]> entry : elements) {
mainOutput.writeInt(entry.getValue().length);
saveExtensions((IExtension[]) objectManager.getHandles(entry.getValue(), RegistryObjectManager.EXTENSION), mainOutput);
}
outputOrphan.flush();
fosOrphan.getFD().sync();
outputOrphan.close();
}
private void log(Status status) {
registry.log(status);
}
// Filters out registry objects that should not be cached
private int[] filter(int[] input) {
boolean[] save = new boolean[input.length];
int resultSize = 0;
for (int i = 0; i < input.length; i++) {
if (objectManager.shouldPersist(input[i])) {
save[i] = true;
resultSize++;
} else
save[i] = false;
}
int[] result = new int[resultSize];
int pos = 0;
for (int i = 0; i < input.length; i++) {
if (save[i]) {
result[pos] = input[i];
pos++;
}
}
return result;
}
}