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