blob: c335061637f65a33835f16b15b998ce45bab0cc9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.core.internal.registry;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.internal.runtime.InternalPlatform;
import org.eclipse.core.internal.runtime.Policy;
import org.eclipse.core.runtime.*;
/**
* Failure reporting strategy:
* - when reading an identified element (bundles, extension points and extensions),
* catch any IOExceptions and rethrow them wrapped into a InvalidRegistryCacheException
* that describes where the error happened
* - IOExceptions while reading non-identified elements (configuration elements and properties)
* are just propagated to their callers
* - the public entry points will catch any exceptions to ensure they are logged and return a valid
* value instead (for instance, a null reference or an empty array)
*/
public class RegistryCacheReader {
MultiStatus problems = null;
// objectTable will be an array list of objects. The objects will be things
// like a plugin descriptor, extension point, etc. The integer
// index value will be used in the cache to allow cross-references in the
// cached registry.
protected List objectTable = null;
private boolean lazilyLoadExtensions;
private boolean flushableExtensions = true;
// indicates we failed load configuration elements lazily
private boolean failed;
protected File cacheFile;
public static final byte REGISTRY_CACHE_VERSION = 7;
public static final byte NULL = 0;
public static final byte OBJECT = 1;
public static final byte INDEX = 2;
public RegistryCacheReader(File cacheFile, MultiStatus problems, boolean lazilyLoadExtensions, boolean flushable) {
super();
this.cacheFile = cacheFile;
this.problems = problems;
this.lazilyLoadExtensions = lazilyLoadExtensions;
this.flushableExtensions = flushable;
objectTable = new ArrayList();
}
public RegistryCacheReader(File cacheFile, MultiStatus problems) {
this(cacheFile, problems, false, true);
}
private int addToObjectTable(Object object) {
objectTable.add(object);
// return the index of the object just added (i.e. size - 1)
return (objectTable.size() - 1);
}
private void debug(String msg) {
System.out.println("RegistryCacheReader: " + msg); //$NON-NLS-1$
}
private boolean readHeaderInformation(DataInputStream in, long expectedTimestamp) throws InvalidRegistryCacheException {
try {
if (in.readInt() != REGISTRY_CACHE_VERSION)
return false;
long installStamp = in.readLong();
long registryStamp = in.readLong();
String osStamp = in.readUTF();
String windowsStamp = in.readUTF();
String localeStamp = in.readUTF();
InternalPlatform info = InternalPlatform.getDefault();
return ((expectedTimestamp == 0 || expectedTimestamp == registryStamp) && (installStamp == info.getStateTimeStamp()) && (osStamp.equals(info.getOS())) && (windowsStamp.equals(info.getWS())) && (localeStamp.equals(info.getNL())));
} catch (IOException e) {
throw new InvalidRegistryCacheException(Policy.bind("meta.regCacheIOExceptionReading", "HeaderInformation"), e); //$NON-NLS-1$//$NON-NLS-2$
}
}
private void skipConfigurationElement(RegistryModelObject parent, DataInputStream in) throws IOException {
readCachedString(in, false); //read name
skipString(in); //skip value
int length = in.readInt();
for (int i = 0; i < length; i++) {
skipConfigurationProperty(in);
}
length = in.readInt();
for (int i = 0; i < length; i++) {
skipConfigurationElement(null, in);
}
}
private ConfigurationElement readConfigurationElement(RegistryModelObject parent, DataInputStream in) throws IOException {
ConfigurationElement result = new ConfigurationElement();
result.setParent(parent);
result.setName(readCachedString(in, false));
result.setValue(readString(in, false));
int length = in.readInt();
ConfigurationProperty[] properties = new ConfigurationProperty[length];
for (int i = 0; i < length; i++) {
properties[i] = readConfigurationProperty(in);
}
result.setProperties(properties);
length = in.readInt();
IConfigurationElement[] elements = new ConfigurationElement[length];
for (int i = 0; i < length; i++) {
elements[i] = readConfigurationElement(result, in);
}
result.setChildren(elements);
return result;
}
private void skipConfigurationProperty(DataInputStream in) throws IOException {
readCachedString(in, false); //Read the name
skipString(in); // skip the value
}
private ConfigurationProperty readConfigurationProperty(DataInputStream in) throws IOException {
String name = readCachedString(in, false);
ConfigurationProperty result = new ConfigurationProperty();
result.setName(name);
result.setValue(readString(in, false));
return result;
}
private Extension readExtension(DataInputStream in) throws InvalidRegistryCacheException {
Extension result = null;
try {
result = (Extension) readIndex(in);
if (result != null)
return result;
result = flushableExtensions ? new FlushableExtension() : new Extension();
addToObjectTable(result);
result.setSimpleIdentifier(readString(in, false));
result.setParent(readBundleModel(in));
result.setName(readString(in, false));
result.setExtensionPointIdentifier(readCachedString(in, false));
result.setSubElements(readSubElements(result, in));
return result;
} catch (IOException e) {
String extensionId = null;
if (result != null && result.getParent() != null)
extensionId = result.getParentIdentifier() + "." + result.getSimpleIdentifier(); //$NON-NLS-1$
throw new InvalidRegistryCacheException(Policy.bind("meta.regCacheIOExceptionReading", "extension: " + extensionId), e); //$NON-NLS-1$ //$NON-NLS-2$
}
}
private ExtensionPoint readExtensionPoint(Namespace bundle, DataInputStream in) throws InvalidRegistryCacheException {
ExtensionPoint result = null;
try {
result = (ExtensionPoint) readIndex(in);
if (result != null)
return result;
result = new ExtensionPoint();
addToObjectTable(result);
result.setParent(bundle);
result.setSimpleIdentifier(readString(in, true));
result.setName(readString(in, false));
result.setSchema(readString(in, false));
// Now do the extensions.
int length = in.readInt();
IExtension[] extensions = new Extension[length];
for (int i = 0; i < length; i++)
extensions[i] = readExtension(in);
result.setExtensions(extensions);
return result;
} catch (IOException e) {
String extensionPointId = null;
if (result != null && result.getParent() != null)
extensionPointId = result.getUniqueIdentifier();
throw new InvalidRegistryCacheException(Policy.bind("meta.regCacheIOExceptionReading", "extension point: " + extensionPointId), e); //$NON-NLS-1$ //$NON-NLS-2$
}
}
private Namespace readBundleModel(DataInputStream in) throws InvalidRegistryCacheException {
Namespace result = null;
try {
result = (Namespace) readIndex(in);
if (result != null)
return result;
result = new Namespace();
addToObjectTable(result);
result.setUniqueIdentifier(readCachedString(in, true));
result.setBundle(InternalPlatform.getDefault().getBundleContext().getBundle(in.readLong()));
result.setParent(readRegistry(in));
result.setHostIdentifier(readCachedString(in, false));
// now do extension points
int length = in.readInt();
IExtensionPoint[] extensionPoints = new ExtensionPoint[length];
for (int i = 0; i < length; i++)
extensionPoints[i] = readExtensionPoint(result, in);
result.setExtensionPoints(extensionPoints);
// and then extensions
length = in.readInt();
IExtension[] extensions = flushableExtensions ? new FlushableExtension[length] : new Extension[length];
for (int i = 0; i < length; i++)
extensions[i] = readExtension(in);
result.setExtensions(extensions);
return result;
} catch (IOException e) {
String bundleId = (result == null || result.getUniqueIdentifier() == null) ? "<not available>" : result.getUniqueIdentifier(); //$NON-NLS-1$
throw new InvalidRegistryCacheException(Policy.bind("meta.regCacheIOExceptionReading", "plugin: " + bundleId), e); //$NON-NLS-1$//$NON-NLS-2$
}
}
private ExtensionRegistry readCache(DataInputStream in, long expectedTimestamps) throws InvalidRegistryCacheException {
if (!readHeaderInformation(in, expectedTimestamps)) {
if (InternalPlatform.DEBUG_REGISTRY)
debug("Cache header information out of date - ignoring cache"); //$NON-NLS-1$
return null;
}
return readRegistry(in);
}
private ExtensionRegistry readRegistry(DataInputStream in) throws InvalidRegistryCacheException {
try {
ExtensionRegistry result = (ExtensionRegistry) readIndex(in);
if (result != null)
return result;
result = new ExtensionRegistry();
if (lazilyLoadExtensions)
result.setCacheReader(this);
addToObjectTable(result);
// if there are no plugins in the registry, return null instead of
// an empty registry?
int length = in.readInt();
if (length == 0)
return null;
for (int i = 0; i < length; i++)
result.basicAdd(readBundleModel(in), false);
if (lazilyLoadExtensions)
result.setCacheReader(this);
return result;
} catch (IOException e) {
throw new InvalidRegistryCacheException(Policy.bind("meta.regCacheIOExceptionReading", "ExtensionRegistry"), e); //$NON-NLS-1$//$NON-NLS-2$
}
}
private ConfigurationElement[] readSubElements(Extension parent, DataInputStream in) throws IOException {
int type = in.readByte();
if (type == NULL)
return null;
//Here type is OBJECT
// the first field is extension sub-elements data offset
int offset = in.readInt();
if (lazilyLoadExtensions) {
Extension extension = parent;
extension.setSubElementsCacheOffset(offset);
skipBasicSubElements(parent, in);
extension.setFullyLoaded(false);
return null;
}
return readBasicSubElements(parent, in);
}
private void skipBasicSubElements(Extension parent, DataInputStream in) throws IOException {
int length = in.readInt();
for (int i = 0; i < length; i++) {
skipConfigurationElement(parent, in);
}
}
private ConfigurationElement[] readBasicSubElements(Extension parent, DataInputStream in) throws IOException {
// read the number of sub elements to load
int length = in.readInt();
ConfigurationElement[] result = new ConfigurationElement[length];
for (int i = 0; i < length; i++) {
result[i] = readConfigurationElement(parent, in);
}
return result;
}
private String readString(DataInputStream in, boolean intern) throws IOException {
byte type = in.readByte();
if (type == NULL)
return null;
if (intern)
return in.readUTF().intern();
else
return in.readUTF();
}
private void skipString(DataInputStream in) throws IOException {
byte type = in.readByte();
if (type == NULL)
return;
int utfLength = in.readUnsignedShort();
byte bytearr[] = new byte[utfLength];
in.readFully(bytearr, 0, utfLength);
}
private String readCachedString(DataInputStream in, boolean intern) throws IOException {
byte type = in.readByte();
if (type == NULL)
return null;
if (type == INDEX)
return (String) objectTable.get(in.readInt());
String stringRead = null;
if (intern)
stringRead = in.readUTF().intern();
else
stringRead = in.readUTF();
addToObjectTable(stringRead);
return stringRead;
}
private Object readIndex(DataInputStream in) throws IOException {
byte type = in.readByte();
return type == INDEX ? objectTable.get(in.readInt()) : null;
}
private DataInputStream openCacheFile() throws IOException {
return new DataInputStream(new BufferedInputStream(new FileInputStream(cacheFile), 2048));
}
/**
* Lazily loads an extension model's sub-elements.
*/
public final ConfigurationElement[] loadConfigurationElements(Extension parent, int offset) {
DataInputStream in = null;
try {
in = openCacheFile();
in.skipBytes(offset);
in.readInt(); // skip the offset itself
return readBasicSubElements(parent, in);
} catch (IOException e) {
Throwable exception = InternalPlatform.DEBUG_REGISTRY ? e : null;
String message = Policy.bind("meta.unableToReadCache"); //$NON-NLS-1$
InternalPlatform.getDefault().log(new Status(IStatus.WARNING, Platform.PI_RUNTIME, 0, message, exception));
} catch (OutOfMemoryError oome) {
// catch any OutOfMemoryErrors that may have been caused by corrupted data
logError(oome);
} catch (RuntimeException re) {
// catch any ArrayIndexOutOfBounds/NullPointer/NegativeArraySize/... exceptions that may have been caused by corrupted data
logError(re);
} finally {
try {
if (in != null)
in.close();
} catch (IOException e) {
// ignore
}
}
// we only get here when we have problems
failed = true;
return new ConfigurationElement[0];
}
boolean hasFailed() {
return failed;
}
private void logError(Throwable t) {
// log general message
String message = Policy.bind("meta.registryCacheReadProblems"); //$NON-NLS-1$
InternalPlatform.getDefault().log(new Status(IStatus.WARNING, Platform.PI_RUNTIME, 0, message, null));
// log actual error
Throwable exceptionToLog = InternalPlatform.DEBUG_REGISTRY ? t : null;
InternalPlatform.getDefault().log(new Status(IStatus.WARNING, Platform.PI_RUNTIME, 0, t.toString(), exceptionToLog));
}
public final ExtensionRegistry loadCache() {
return this.loadCache(0);
}
/*
* If expectedTimestamp != 0, check it against the registry timestamp if the header.
*/
public final ExtensionRegistry loadCache(long expectedTimestamp) {
DataInputStream in = null;
try {
in = openCacheFile();
} catch (IOException e) {
Throwable exception = InternalPlatform.DEBUG_REGISTRY ? e : null;
String message = Policy.bind("meta.unableToReadCache"); //$NON-NLS-1$
InternalPlatform.getDefault().log(new Status(IStatus.WARNING, Platform.PI_RUNTIME, 0, message, exception));
return null;
}
try {
return readCache(in, expectedTimestamp);
} catch (InvalidRegistryCacheException e) {
Throwable exception = InternalPlatform.DEBUG_REGISTRY ? e.getCause() : null;
InternalPlatform.getDefault().log(new Status(IStatus.WARNING, Platform.PI_RUNTIME, 0, e.getMessage(), exception));
} catch (OutOfMemoryError oome) {
// catch any OutOfMemoryErrors that may have been caused by corrupted data
logError(oome);
} catch (RuntimeException re) {
// catch any ArrayIndexOutOfBounds/NullPointer/NegativeArraySize/... exceptions that may have been caused by corrupted data
logError(re);
} finally {
try {
if (in != null)
in.close();
} catch (IOException e) {
// ignore
}
}
return null;
}
public class InvalidRegistryCacheException extends Exception {
/**
* All serializable objects should have a stable serialVersionUID
*/
private static final long serialVersionUID = 1L;
Throwable cause = null;
public InvalidRegistryCacheException(String msg, Throwable cause) {
super(msg);
this.cause = cause;
}
public InvalidRegistryCacheException(String string) {
super(string);
}
public Throwable getCause() {
return cause;
}
}
}