1 /*******************************************************************************
2  * Copyright (c) 2004, 2018 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.core.internal.registry;
15 
16 import java.io.*;
17 import java.lang.ref.SoftReference;
18 import java.nio.charset.StandardCharsets;
19 import java.util.HashMap;
20 import java.util.Map;
21 import org.eclipse.core.runtime.IStatus;
22 import org.eclipse.core.runtime.Status;
23 import org.eclipse.core.runtime.spi.RegistryContributor;
24 import org.eclipse.osgi.util.NLS;
25 
26 public class TableReader {
27 	//Markers in the cache
28 	static final int NULL = 0;
29 	static final int OBJECT = 1;
30 	static final int LOBJECT = 2;
31 
32 	//The version of the cache
33 	static final int CACHE_VERSION = 8;
34 	// Version 1 -> 2: the contributor Ids changed from "long" to "String"
35 	// Version 2 -> 3: added namespace index and the table of contributors
36 	// Version 3 -> 4: offset table saved in a binary form (performance)
37 	// Version 4 -> 5: remove support added in version 4 to save offset table in a binary form (performance)
38 	// Version 5 -> 6: replace HashtableOfInt with OffsetTable (memory usage optimization)
39 	// Version 6 -> 7: added option for multi-language support
40 	// Version 7 -> 8: added support for large UTF-8 strings
41 
42 	//Informations representing the MAIN file
43 	static final String MAIN = ".mainData"; //$NON-NLS-1$
44 	BufferedRandomInputStream mainDataFile = null;
45 	DataInputStream mainInput = null;
46 
47 	//Informations representing the EXTRA file
48 	static final String EXTRA = ".extraData"; //$NON-NLS-1$
49 	BufferedRandomInputStream extraDataFile = null;
50 	DataInputStream extraInput = null;
51 
52 	//The table file
53 	static final String TABLE = ".table"; //$NON-NLS-1$
54 	File tableFile;
55 
56 	//The contributions file
57 	static final String CONTRIBUTIONS = ".contributions"; //$NON-NLS-1$
58 	File contributionsFile;
59 
60 	//The contributor file
61 	static final String CONTRIBUTORS = ".contributors"; //$NON-NLS-1$
62 	File contributorsFile;
63 
64 	//The namespace file
65 	static final String NAMESPACES = ".namespaces"; //$NON-NLS-1$
66 	File namespacesFile;
67 
68 	//The orphan file
69 	static final String ORPHANS = ".orphans"; //$NON-NLS-1$
70 	File orphansFile;
71 
72 	//Status code
73 	private static final byte fileError = 0;
74 	private static final boolean DEBUG = false; //TODO need to change
75 
76 	private boolean holdObjects = false;
77 
78 	private final ExtensionRegistry registry;
79 
80 	private SoftReference<Map<String, String>> stringPool;
81 
setMainDataFile(File main)82 	void setMainDataFile(File main) throws IOException {
83 		mainDataFile = new BufferedRandomInputStream(main);
84 		mainInput = new DataInputStream(mainDataFile);
85 	}
86 
setExtraDataFile(File extra)87 	void setExtraDataFile(File extra) throws IOException {
88 		extraDataFile = new BufferedRandomInputStream(extra);
89 		extraInput = new DataInputStream(extraDataFile);
90 	}
91 
setTableFile(File table)92 	void setTableFile(File table) {
93 		tableFile = table;
94 	}
95 
setContributionsFile(File namespace)96 	void setContributionsFile(File namespace) {
97 		contributionsFile = namespace;
98 	}
99 
setContributorsFile(File file)100 	void setContributorsFile(File file) {
101 		contributorsFile = file;
102 	}
103 
setNamespacesFile(File file)104 	void setNamespacesFile(File file) {
105 		namespacesFile = file;
106 	}
107 
setOrphansFile(File orphan)108 	void setOrphansFile(File orphan) {
109 		orphansFile = orphan;
110 	}
111 
TableReader(ExtensionRegistry registry)112 	public TableReader(ExtensionRegistry registry) {
113 		this.registry = registry;
114 	}
115 
116 	// Don't need to synchronize - called only from a synchronized method
loadTables(long expectedTimestamp)117 	public Object[] loadTables(long expectedTimestamp) {
118 		HashtableOfStringAndInt extensionPoints;
119 
120 		DataInputStream tableInput = null;
121 		try {
122 			tableInput = new DataInputStream(new BufferedInputStream(new FileInputStream(tableFile)));
123 			if (!checkCacheValidity(tableInput, expectedTimestamp))
124 				return null;
125 
126 			Integer nextId = Integer.valueOf(tableInput.readInt());
127 			OffsetTable offsets = OffsetTable.load(tableInput);
128 			extensionPoints = new HashtableOfStringAndInt();
129 			extensionPoints.load(tableInput);
130 			return new Object[] {offsets, extensionPoints, nextId};
131 		} catch (IOException e) {
132 			log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, RegistryMessages.meta_registryCacheReadProblems, e));
133 			return null;
134 		} finally {
135 			if (tableInput != null)
136 				try {
137 					tableInput.close();
138 				} catch (IOException e1) {
139 					//Ignore
140 				}
141 		}
142 
143 	}
144 
145 	//	Check various aspect of the cache to see if it's valid
checkCacheValidity(DataInputStream in, long expectedTimestamp)146 	private boolean checkCacheValidity(DataInputStream in, long expectedTimestamp) {
147 		int version;
148 		try {
149 			version = in.readInt();
150 			if (version != CACHE_VERSION)
151 				return false;
152 
153 			long installStamp = in.readLong();
154 			long registryStamp = in.readLong();
155 			long mainDataFileSize = in.readLong();
156 			long extraDataFileSize = in.readLong();
157 			long contributionsFileSize = in.readLong();
158 			long contributorsFileSize = in.readLong();
159 			long namespacesFileSize = in.readLong();
160 			long orphansFileSize = in.readLong();
161 			String osStamp = readUTF(in, OBJECT);
162 			String windowsStamp = readUTF(in, OBJECT);
163 			String localeStamp = readUTF(in, OBJECT);
164 			boolean multiLanguage = in.readBoolean();
165 
166 			boolean validTime = (expectedTimestamp == 0 || expectedTimestamp == registryStamp);
167 			boolean validInstall = (installStamp == registry.computeState());
168 			boolean validOS = (osStamp.equals(RegistryProperties.getProperty(IRegistryConstants.PROP_OS, RegistryProperties.empty)));
169 			boolean validWS = (windowsStamp.equals(RegistryProperties.getProperty(IRegistryConstants.PROP_WS, RegistryProperties.empty)));
170 			boolean validNL = (localeStamp.equals(RegistryProperties.getProperty(IRegistryConstants.PROP_NL, RegistryProperties.empty)));
171 			boolean validMultiLang = (registry.isMultiLanguage() == multiLanguage);
172 
173 			if (!validTime || !validInstall || !validOS || !validWS || !validNL || !validMultiLang)
174 				return false;
175 
176 			boolean validMain = (mainDataFileSize == mainDataFile.length());
177 			boolean validExtra = (extraDataFileSize == extraDataFile.length());
178 			boolean validContrib = (contributionsFileSize == contributionsFile.length());
179 			boolean validContributors = (contributorsFileSize == contributorsFile.length());
180 			boolean validNamespace = (namespacesFileSize == namespacesFile.length());
181 			boolean validOrphan = (orphansFileSize == orphansFile.length());
182 
183 			return (validMain && validExtra && validContrib && validContributors && validNamespace && validOrphan);
184 		} catch (IOException e) {
185 			log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, RegistryMessages.meta_registryCacheInconsistent, e));
186 			return false;
187 		}
188 	}
189 
loadConfigurationElement(int offset)190 	public Object loadConfigurationElement(int offset) {
191 		try {
192 			synchronized (mainDataFile) {
193 				goToInputFile(offset);
194 				return basicLoadConfigurationElement(mainInput, null);
195 			}
196 		} catch (IOException e) {
197 			String message = NLS.bind(RegistryMessages.meta_regCacheIOExceptionReading, mainDataFile);
198 			log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, message, e));
199 			if (DEBUG)
200 				log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, "Error reading a configuration element (" + offset + ") from the registry cache", e)); //$NON-NLS-1$//$NON-NLS-2$
201 			return null;
202 		}
203 	}
204 
basicLoadConfigurationElement(DataInputStream is, String actualContributorId)205 	private ConfigurationElement basicLoadConfigurationElement(DataInputStream is, String actualContributorId) throws IOException {
206 		int self = is.readInt();
207 		String contributorId = readStringOrNull(is);
208 		String name = readStringOrNull(is);
209 		int parentId = is.readInt();
210 		byte parentType = is.readByte();
211 		int misc = is.readInt();//this is set in second level CEs, to indicate where in the extra data file the children CEs are
212 		String[] propertiesAndValue = readPropertiesAndValue(is);
213 		int[] children = readArray(is);
214 		if (actualContributorId == null)
215 			actualContributorId = contributorId;
216 		ConfigurationElement result = getObjectFactory().createConfigurationElement(self, actualContributorId, name, propertiesAndValue, children, misc, parentId, parentType, true);
217 		if (registry.isMultiLanguage()) { // cache is multi-language too or it would have failed validation
218 			int numberOfLocales = is.readInt();
219 			DirectMap translated = null;
220 			if (numberOfLocales != 0) {
221 				translated = new DirectMap(numberOfLocales, 0.5f);
222 				String[] NLs = readStringArray(is);
223 				for (int i = 0; i < numberOfLocales; i++) {
224 					String[] translatedProperties = readStringArray(is);
225 					translated.put(NLs[i], translatedProperties);
226 				}
227 			}
228 			ConfigurationElementMulti multiCE = (ConfigurationElementMulti) result;
229 			if (translated != null)
230 				multiCE.setTranslatedProperties(translated);
231 		}
232 		return result;
233 	}
234 
readStringArray(DataInputStream is)235 	private String[] readStringArray(DataInputStream is) throws IOException {
236 		int size = is.readInt();
237 		if (size == 0)
238 			return null;
239 		String[] result = new String[size];
240 		for (int i = 0; i < size; i++) {
241 			result[i] = readStringOrNull(is);
242 		}
243 		return result;
244 	}
245 
loadThirdLevelConfigurationElements(int offset, RegistryObjectManager objectManager)246 	public Object loadThirdLevelConfigurationElements(int offset, RegistryObjectManager objectManager) {
247 		try {
248 			synchronized (extraDataFile) {
249 				goToExtraFile(offset);
250 				return loadConfigurationElementAndChildren(null, extraInput, 3, Integer.MAX_VALUE, objectManager, null);
251 			}
252 		} catch (IOException e) {
253 			String message = NLS.bind(RegistryMessages.meta_regCacheIOExceptionReading, extraDataFile);
254 			log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, message, e));
255 			if (DEBUG)
256 				log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, "Error reading a third level configuration element (" + offset + ") from the registry cache", e)); //$NON-NLS-1$//$NON-NLS-2$
257 			return null;
258 		}
259 	}
260 
261 	//Read a whole configuration element subtree
loadConfigurationElementAndChildren(DataInputStream is, DataInputStream extraIs, int depth, int maxDepth, RegistryObjectManager objectManager, String namespaceOwnerId)262 	private ConfigurationElement loadConfigurationElementAndChildren(DataInputStream is, DataInputStream extraIs, int depth, int maxDepth, RegistryObjectManager objectManager, String namespaceOwnerId) throws IOException {
263 		DataInputStream currentStream = is;
264 		if (depth > 2)
265 			currentStream = extraIs;
266 
267 		ConfigurationElement ce = basicLoadConfigurationElement(currentStream, namespaceOwnerId);
268 		if (namespaceOwnerId == null)
269 			namespaceOwnerId = ce.getContributorId();
270 		int[] children = ce.getRawChildren();
271 		if (depth + 1 > maxDepth)
272 			return ce;
273 
274 		for (int i = 0; i < children.length; i++) {
275 			ConfigurationElement tmp = loadConfigurationElementAndChildren(currentStream, extraIs, depth + 1, maxDepth, objectManager, namespaceOwnerId);
276 			objectManager.add(tmp, holdObjects);
277 		}
278 		return ce;
279 	}
280 
readPropertiesAndValue(DataInputStream inputStream)281 	private String[] readPropertiesAndValue(DataInputStream inputStream) throws IOException {
282 		int numberOfProperties = inputStream.readInt();
283 		if (numberOfProperties == 0)
284 			return RegistryObjectManager.EMPTY_STRING_ARRAY;
285 		String[] properties = new String[numberOfProperties];
286 		for (int i = 0; i < numberOfProperties; i++) {
287 			properties[i] = readStringOrNull(inputStream);
288 		}
289 		return properties;
290 	}
291 
loadExtension(int offset)292 	public Object loadExtension(int offset) {
293 		try {
294 			synchronized (mainDataFile) {
295 				goToInputFile(offset);
296 				return basicLoadExtension(mainInput);
297 			}
298 		} catch (IOException e) {
299 			String message = NLS.bind(RegistryMessages.meta_regCacheIOExceptionReading, mainDataFile);
300 			log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, message, e));
301 			if (DEBUG)
302 				log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, "Error reading an extension (" + offset + ") from the registry cache", e)); //$NON-NLS-1$//$NON-NLS-2$
303 		}
304 		return null;
305 	}
306 
basicLoadExtension(DataInputStream inputStream)307 	private Extension basicLoadExtension(DataInputStream inputStream) throws IOException {
308 		int self = inputStream.readInt();
309 		String simpleId = readStringOrNull(mainInput);
310 		String namespace = readStringOrNull(mainInput);
311 		int[] children = readArray(mainInput);
312 		int extraData = mainInput.readInt();
313 		return getObjectFactory().createExtension(self, simpleId, namespace, children, extraData, true);
314 	}
315 
loadExtensionPointTree(int offset, RegistryObjectManager objects)316 	public ExtensionPoint loadExtensionPointTree(int offset, RegistryObjectManager objects) {
317 		try {
318 			synchronized (mainDataFile) {
319 				ExtensionPoint xpt = (ExtensionPoint) loadExtensionPoint(offset);
320 				int[] children = xpt.getRawChildren();
321 				int nbrOfExtension = children.length;
322 				for (int i = 0; i < nbrOfExtension; i++) {
323 					Extension loaded = basicLoadExtension(mainInput);
324 					objects.add(loaded, holdObjects);
325 				}
326 
327 				for (int i = 0; i < nbrOfExtension; i++) {
328 					int nbrOfCe = mainInput.readInt();
329 					for (int j = 0; j < nbrOfCe; j++) {
330 						// note that max depth is set to 2 and extra input is never going to
331 						// be used in this call to the loadConfigurationElementAndChildren().
332 						objects.add(loadConfigurationElementAndChildren(mainInput, null, 1, 2, objects, null), holdObjects);
333 					}
334 				}
335 				return xpt;
336 			}
337 		} catch (IOException e) {
338 			String message = NLS.bind(RegistryMessages.meta_regCacheIOExceptionReading, mainDataFile);
339 			log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, message, e));
340 			if (DEBUG)
341 				log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, "Error reading an extension point tree (" + offset + ") from the registry cache", e)); //$NON-NLS-1$//$NON-NLS-2$
342 			return null;
343 		}
344 	}
345 
loadExtensionPoint(int offset)346 	private Object loadExtensionPoint(int offset) {
347 		try {
348 			goToInputFile(offset);
349 			return basicLoadExtensionPoint();
350 		} catch (IOException e) {
351 			String message = NLS.bind(RegistryMessages.meta_regCacheIOExceptionReading, mainDataFile);
352 			log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, message, e));
353 			if (DEBUG)
354 				log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, "Error reading an extension point (" + offset + ") from the registry cache", e)); //$NON-NLS-1$ //$NON-NLS-2$
355 			return null;
356 		}
357 	}
358 
basicLoadExtensionPoint()359 	private ExtensionPoint basicLoadExtensionPoint() throws IOException {
360 		int self = mainInput.readInt();
361 		int[] children = readArray(mainInput);
362 		int extraData = mainInput.readInt();
363 		return getObjectFactory().createExtensionPoint(self, children, extraData, true);
364 	}
365 
readArray(DataInputStream in)366 	private int[] readArray(DataInputStream in) throws IOException {
367 		int arraySize = in.readInt();
368 		if (arraySize == 0)
369 			return RegistryObjectManager.EMPTY_INT_ARRAY;
370 		int[] result = new int[arraySize];
371 		for (int i = 0; i < arraySize; i++) {
372 			result[i] = in.readInt();
373 		}
374 		return result;
375 	}
376 
goToInputFile(int offset)377 	private void goToInputFile(int offset) throws IOException {
378 		mainDataFile.seek(offset);
379 	}
380 
goToExtraFile(int offset)381 	private void goToExtraFile(int offset) throws IOException {
382 		extraDataFile.seek(offset);
383 	}
384 
readStringOrNull(DataInputStream in)385 	private String readStringOrNull(DataInputStream in) throws IOException {
386 		byte type = in.readByte();
387 		if (type == NULL)
388 			return null;
389 		return readUTF(in, type);
390 	}
391 
loadExtensionExtraData(int dataPosition)392 	public String[] loadExtensionExtraData(int dataPosition) {
393 		try {
394 			synchronized (extraDataFile) {
395 				goToExtraFile(dataPosition);
396 				return basicLoadExtensionExtraData();
397 			}
398 		} catch (IOException e) {
399 			String message = NLS.bind(RegistryMessages.meta_regCacheIOExceptionReading, extraDataFile);
400 			log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, message, e));
401 			if (DEBUG)
402 				log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, "Error reading extension label (" + dataPosition + ") from the registry cache", e)); //$NON-NLS-1$ //$NON-NLS-2$
403 			return null;
404 		}
405 	}
406 
basicLoadExtensionExtraData()407 	private String[] basicLoadExtensionExtraData() throws IOException {
408 		return new String[] {readStringOrNull(extraInput), readStringOrNull(extraInput), readStringOrNull(extraInput)};
409 	}
410 
loadExtensionPointExtraData(int offset)411 	public String[] loadExtensionPointExtraData(int offset) {
412 		try {
413 			synchronized (extraDataFile) {
414 				goToExtraFile(offset);
415 				return basicLoadExtensionPointExtraData();
416 			}
417 		} catch (IOException e) {
418 			String message = NLS.bind(RegistryMessages.meta_regCacheIOExceptionReading, extraDataFile);
419 			log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, message, e));
420 			if (DEBUG)
421 				log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, "Error reading extension point data (" + offset + ") from the registry cache", e)); //$NON-NLS-1$ //$NON-NLS-2$
422 			return null;
423 		}
424 	}
425 
basicLoadExtensionPointExtraData()426 	private String[] basicLoadExtensionPointExtraData() throws IOException {
427 		String[] result = new String[5];
428 		result[0] = readStringOrNull(extraInput); //the label
429 		result[1] = readStringOrNull(extraInput); //the schema
430 		result[2] = readStringOrNull(extraInput); //the fully qualified name
431 		result[3] = readStringOrNull(extraInput); //the namespace
432 		result[4] = readStringOrNull(extraInput); //the contributor Id
433 		return result;
434 	}
435 
loadContributions()436 	public KeyedHashSet loadContributions() {
437 		DataInputStream namespaceInput = null;
438 		try {
439 			synchronized (contributionsFile) {
440 				namespaceInput = new DataInputStream(new BufferedInputStream(new FileInputStream(contributionsFile)));
441 				int size = namespaceInput.readInt();
442 				KeyedHashSet result = new KeyedHashSet(size);
443 				for (int i = 0; i < size; i++) {
444 					String contributorId = readStringOrNull(namespaceInput);
445 					Contribution n = getObjectFactory().createContribution(contributorId, true);
446 					n.setRawChildren(readArray(namespaceInput));
447 					result.add(n);
448 				}
449 				return result;
450 			}
451 		} catch (IOException e) {
452 			String message = NLS.bind(RegistryMessages.meta_regCacheIOExceptionReading, contributionsFile);
453 			log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, message, e));
454 			return null;
455 		} finally {
456 			if (namespaceInput != null)
457 				try {
458 					namespaceInput.close();
459 				} catch (IOException e1) {
460 					//Ignore
461 				}
462 		}
463 	}
464 
465 	final static float contributorsLoadFactor = 1.2f; // allocate more memory to avoid resizing
466 
loadContributors()467 	public HashMap<String, RegistryContributor> loadContributors() {
468 		HashMap<String, RegistryContributor> result = null;
469 		DataInputStream contributorsInput = null;
470 		try {
471 			synchronized (contributorsFile) {
472 				contributorsInput = new DataInputStream(new BufferedInputStream(new FileInputStream(contributorsFile)));
473 				int size = contributorsInput.readInt();
474 				result = new HashMap<>((int) (size * contributorsLoadFactor));
475 				for (int i = 0; i < size; i++) {
476 					String id = readStringOrNull(contributorsInput);
477 					String name = readStringOrNull(contributorsInput);
478 					String hostId = readStringOrNull(contributorsInput);
479 					String hostName = readStringOrNull(contributorsInput);
480 					result.put(id, new RegistryContributor(id, name, hostId, hostName));
481 				}
482 			}
483 			return result;
484 		} catch (IOException e) {
485 			String message = NLS.bind(RegistryMessages.meta_regCacheIOExceptionReading, contributorsFile);
486 			log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, message, e));
487 			return null;
488 		} finally {
489 			if (contributorsInput != null)
490 				try {
491 					contributorsInput.close();
492 				} catch (IOException e1) {
493 					//Ignore
494 				}
495 		}
496 	}
497 
loadNamespaces()498 	public KeyedHashSet loadNamespaces() {
499 		DataInputStream namespaceInput = null;
500 		try {
501 			synchronized (namespacesFile) {
502 				namespaceInput = new DataInputStream(new BufferedInputStream(new FileInputStream(namespacesFile)));
503 				int size = namespaceInput.readInt();
504 				KeyedHashSet result = new KeyedHashSet(size);
505 				for (int i = 0; i < size; i++) {
506 					String key = readStringOrNull(namespaceInput);
507 					RegistryIndexElement indexElement = new RegistryIndexElement(key);
508 					indexElement.updateExtensionPoints(readArray(namespaceInput), true);
509 					indexElement.updateExtensions(readArray(namespaceInput), true);
510 					result.add(indexElement);
511 				}
512 				return result;
513 			}
514 		} catch (IOException e) {
515 			String message = NLS.bind(RegistryMessages.meta_regCacheIOExceptionReading, namespacesFile);
516 			log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, message, e));
517 			return null;
518 		} finally {
519 			if (namespaceInput != null)
520 				try {
521 					namespaceInput.close();
522 				} catch (IOException e1) {
523 					//Ignore
524 				}
525 		}
526 	}
527 
loadAllOrphans(RegistryObjectManager objectManager)528 	private void loadAllOrphans(RegistryObjectManager objectManager) throws IOException {
529 		//Read the extensions and configuration elements of the orphans
530 		int orphans = objectManager.getOrphanExtensions().size();
531 		for (int k = 0; k < orphans; k++) {
532 			int numberOfOrphanExtensions = mainInput.readInt();
533 			for (int i = 0; i < numberOfOrphanExtensions; i++) {
534 				loadFullExtension(objectManager);
535 			}
536 			for (int i = 0; i < numberOfOrphanExtensions; i++) {
537 				int nbrOfCe = mainInput.readInt();
538 				for (int j = 0; j < nbrOfCe; j++) {
539 					objectManager.add(loadConfigurationElementAndChildren(mainInput, extraInput, 1, Integer.MAX_VALUE, objectManager, null), true);
540 				}
541 			}
542 		}
543 	}
544 
545 	// Do not need to synchronize - called only from a synchronized method
readAllCache(RegistryObjectManager objectManager)546 	public boolean readAllCache(RegistryObjectManager objectManager) {
547 		try {
548 			int size = objectManager.getExtensionPoints().size();
549 			for (int i = 0; i < size; i++) {
550 				objectManager.add(readAllExtensionPointTree(objectManager), holdObjects);
551 			}
552 			loadAllOrphans(objectManager);
553 		} catch (IOException e) {
554 			String message = NLS.bind(RegistryMessages.meta_regCacheIOExceptionReading, mainDataFile);
555 			log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, message, e));
556 			return false;
557 		}
558 		return true;
559 	}
560 
readAllExtensionPointTree(RegistryObjectManager objectManager)561 	private ExtensionPoint readAllExtensionPointTree(RegistryObjectManager objectManager) throws IOException {
562 		ExtensionPoint xpt = loadFullExtensionPoint();
563 		int[] children = xpt.getRawChildren();
564 		int nbrOfExtension = children.length;
565 		for (int i = 0; i < nbrOfExtension; i++) {
566 			loadFullExtension(objectManager);
567 		}
568 
569 		for (int i = 0; i < nbrOfExtension; i++) {
570 			int nbrOfCe = mainInput.readInt();
571 			for (int j = 0; j < nbrOfCe; j++) {
572 				objectManager.add(loadConfigurationElementAndChildren(mainInput, extraInput, 1, Integer.MAX_VALUE, objectManager, null), true);
573 			}
574 		}
575 		return xpt;
576 	}
577 
loadFullExtensionPoint()578 	private ExtensionPoint loadFullExtensionPoint() throws IOException { //TODO I don't like this.
579 		ExtensionPoint xpt = basicLoadExtensionPoint();
580 		String[] tmp = basicLoadExtensionPointExtraData();
581 		xpt.setLabel(tmp[0]);
582 		xpt.setSchema(tmp[1]);
583 		xpt.setUniqueIdentifier(tmp[2]);
584 		xpt.setNamespace(tmp[3]);
585 		xpt.setContributorId(tmp[4]);
586 		return xpt;
587 	}
588 
loadFullExtension(RegistryObjectManager objectManager)589 	private Extension loadFullExtension(RegistryObjectManager objectManager) throws IOException {
590 		String[] tmp;
591 		Extension loaded = basicLoadExtension(mainInput);
592 		tmp = basicLoadExtensionExtraData();
593 		loaded.setLabel(tmp[0]);
594 		loaded.setExtensionPointIdentifier(tmp[1]);
595 		loaded.setContributorId(tmp[2]);
596 		objectManager.add(loaded, holdObjects);
597 		return loaded;
598 	}
599 
loadOrphans()600 	public HashMap<String, int[]> loadOrphans() {
601 		DataInputStream orphanInput = null;
602 		try {
603 			synchronized (orphansFile) {
604 				orphanInput = new DataInputStream(new BufferedInputStream(new FileInputStream(orphansFile)));
605 				int size = orphanInput.readInt();
606 				HashMap<String, int[]> result = new HashMap<>(size);
607 				for (int i = 0; i < size; i++) {
608 					String key = readUTF(orphanInput, OBJECT);
609 					int[] value = readArray(orphanInput);
610 					result.put(key, value);
611 				}
612 				return result;
613 			}
614 		} catch (IOException e) {
615 			return null;
616 		} finally {
617 			if (orphanInput != null)
618 				try {
619 					orphanInput.close();
620 				} catch (IOException e1) {
621 					//ignore
622 				}
623 		}
624 	}
625 
626 	// Don't need to synchronize - called only from a synchronized method
setHoldObjects(boolean holdObjects)627 	public void setHoldObjects(boolean holdObjects) {
628 		this.holdObjects = holdObjects;
629 	}
630 
log(Status status)631 	private void log(Status status) {
632 		registry.log(status);
633 	}
634 
getObjectFactory()635 	private RegistryObjectFactory getObjectFactory() {
636 		return registry.getElementFactory();
637 	}
638 
639 	// Returns a file name used to test if cache is actually present at a given location
getTestFileName()640 	public static String getTestFileName() {
641 		return TABLE;
642 	}
643 
close()644 	public void close() {
645 		try {
646 			if (mainInput != null)
647 				mainInput.close();
648 			if (extraInput != null)
649 				extraInput.close();
650 		} catch (IOException e) {
651 			log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, RegistryMessages.meta_registryCacheReadProblems, e));
652 		}
653 	}
654 
readUTF(DataInputStream in, int type)655 	private String readUTF(DataInputStream in, int type) throws IOException {
656 		String value;
657 		if (type == LOBJECT) {
658 			int length = in.readInt();
659 			byte[] data = new byte[length];
660 			in.readFully(data);
661 			value = new String(data, StandardCharsets.UTF_8);
662 		} else {
663 			value = in.readUTF();
664 		}
665 
666 		Map<String, String> map = null;
667 		if (stringPool != null) {
668 			map = stringPool.get();
669 		}
670 		if (map == null) {
671 			map = new HashMap<>();
672 			stringPool = new SoftReference<>(map);
673 		}
674 
675 		String pooledString = map.get(value);
676 		if (pooledString == null) {
677 			map.put(value, value);
678 			return value;
679 		}
680 
681 		return pooledString;
682 	}
683 }
684