package org.eclipse.osgi.internal.debug;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.eclipse.osgi.internal.framework.EquinoxConfiguration;
import org.eclipse.osgi.internal.location.LocationHelper;
import org.eclipse.osgi.service.debug.DebugOptions;
import org.eclipse.osgi.service.debug.DebugOptionsListener;
import org.eclipse.osgi.service.debug.DebugTrace;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
public class FrameworkDebugOptions implements DebugOptions, ServiceTrackerCustomizer<DebugOptionsListener, DebugOptionsListener> {
private static final String OSGI_DEBUG = "osgi.debug";
private static final String OSGI_DEBUG_VERBOSE = "osgi.debug.verbose";
public static final String PROP_TRACEFILE = "osgi.tracefile";
private static final String OPTIONS = ".options";
private final static Object writeLock = new Object();
private final Object lock = new Object();
private Properties options = null;
private Properties disabledOptions = null;
protected final Map<String, DebugTrace> debugTraceCache = new HashMap<>();
protected File outFile = null;
protected boolean verboseDebug = true;
private boolean newSession = true;
private final EquinoxConfiguration environmentInfo;
private volatile BundleContext context;
private volatile ServiceTracker<DebugOptionsListener, DebugOptionsListener> listenerTracker;
public FrameworkDebugOptions(EquinoxConfiguration environmentInfo) {
this.environmentInfo = environmentInfo;
this.verboseDebug = Boolean.valueOf(environmentInfo.getConfiguration(OSGI_DEBUG_VERBOSE, Boolean.TRUE.toString())).booleanValue();
String debugOptionsFilename = environmentInfo.getConfiguration(OSGI_DEBUG);
if (debugOptionsFilename == null)
return;
options = new Properties();
URL optionsFile;
if (debugOptionsFilename.length() == 0) {
String userDir = System.getProperty("user.dir").replace(File.separatorChar, '/');
if (!userDir.endsWith("/"))
userDir += "/";
debugOptionsFilename = new File(userDir, OPTIONS).toString();
}
optionsFile = LocationHelper.buildURL(debugOptionsFilename, false);
if (optionsFile == null) {
System.out.println("Unable to construct URL for options file: " + debugOptionsFilename);
return;
}
System.out.print("Debug options:\n " + optionsFile.toExternalForm());
try {
InputStream input = LocationHelper.getStream(optionsFile);
try {
options.load(input);
System.out.println(" loaded");
} finally {
input.close();
}
} catch (FileNotFoundException e) {
System.out.println(" not found");
} catch (IOException e) {
System.out.println(" did not parse");
e.printStackTrace(System.out);
}
for (Object key : options.keySet()) {
options.put(key, ((String) options.get(key)).trim());
}
}
public void start(BundleContext bc) {
this.context = bc;
listenerTracker = new ServiceTracker<>(bc, DebugOptionsListener.class.getName(), this);
listenerTracker.open();
}
public void stop(BundleContext bc) {
listenerTracker.close();
listenerTracker = null;
this.context = null;
}
@Override
public boolean getBooleanOption(String option, boolean defaultValue) {
String optionValue = getOption(option);
return optionValue != null ? optionValue.equalsIgnoreCase("true") : defaultValue;
}
@Override
public String getOption(String option) {
return getOption(option, null);
}
@Override
public String getOption(String option, String defaultValue) {
synchronized (lock) {
if (options != null) {
return options.getProperty(option, defaultValue);
}
}
return defaultValue;
}
@Override
public int getIntegerOption(String option, int defaultValue) {
String value = getOption(option);
try {
return value == null ? defaultValue : Integer.parseInt(value);
} catch (NumberFormatException e) {
return defaultValue;
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public Map<String, String> getOptions() {
Map<String, String> snapShot = new HashMap<>();
synchronized (lock) {
if (options != null)
snapShot.putAll((Map) options);
else if (disabledOptions != null)
snapShot.putAll((Map) disabledOptions);
}
return snapShot;
}
String[] getAllOptions() {
String[] optionsArray = null;
synchronized (lock) {
if (options != null) {
optionsArray = new String[options.size()];
final Iterator<Map.Entry<Object, Object>> entrySetIterator = options.entrySet().iterator();
int i = 0;
while (entrySetIterator.hasNext()) {
Map.Entry<Object, Object> entry = entrySetIterator.next();
optionsArray[i] = ((String) entry.getKey()) + "=" + ((String) entry.getValue());
i++;
}
}
}
if (optionsArray == null) {
optionsArray = new String[1];
}
return optionsArray;
}
@Override
public void removeOption(String option) {
if (option == null)
return;
String fireChangedEvent = null;
synchronized (lock) {
if (options != null && options.remove(option) != null) {
fireChangedEvent = getSymbolicName(option);
}
}
if (fireChangedEvent != null) {
optionsChanged(fireChangedEvent);
}
}
@Override
public void setOption(String option, String value) {
if (option == null || value == null) {
throw new IllegalArgumentException("The option and value must not be null.");
}
String fireChangedEvent = null;
value = value != null ? value.trim() : null;
synchronized (lock) {
if (options != null) {
String currentValue = options.getProperty(option);
if (currentValue != null) {
if (!currentValue.equals(value)) {
fireChangedEvent = getSymbolicName(option);
}
} else {
if (value != null) {
fireChangedEvent = getSymbolicName(option);
}
}
if (fireChangedEvent != null) {
options.put(option, value);
}
}
}
if (fireChangedEvent != null) {
optionsChanged(fireChangedEvent);
}
}
private String getSymbolicName(String option) {
int firstSlashIndex = option.indexOf('/');
if (firstSlashIndex > 0)
return option.substring(0, firstSlashIndex);
return null;
}
@SuppressWarnings("cast")
@Override
public void setOptions(Map<String, String> ops) {
if (ops == null)
throw new IllegalArgumentException("The options must not be null.");
Properties newOptions = new Properties();
for (Map.Entry<String, String> entry : ops.entrySet()) {
if (!(entry.getKey() instanceof String) || !(entry.getValue() instanceof String))
throw new IllegalArgumentException("Option keys and values must be of type String: " + entry.getKey() + "=" + entry.getValue());
newOptions.put(entry.getKey(), entry.getValue().trim());
}
Set<String> fireChangesTo = null;
synchronized (lock) {
if (options == null) {
disabledOptions = newOptions;
return;
}
fireChangesTo = new HashSet<>();
for (Iterator<Object> keys = options.keySet().iterator(); keys.hasNext();) {
String key = (String) keys.next();
if (!newOptions.containsKey(key)) {
String symbolicName = getSymbolicName(key);
if (symbolicName != null)
fireChangesTo.add(symbolicName);
}
}
for (Map.Entry<Object, Object> entry : newOptions.entrySet()) {
String existingValue = (String) options.get(entry.getKey());
if (!entry.getValue().equals(existingValue)) {
String symbolicName = getSymbolicName((String) entry.getKey());
if (symbolicName != null)
fireChangesTo.add(symbolicName);
}
}
options = newOptions;
}
if (fireChangesTo != null)
for (Iterator<String> iChanges = fireChangesTo.iterator(); iChanges.hasNext();)
optionsChanged(iChanges.next());
}
@Override
public boolean isDebugEnabled() {
synchronized (lock) {
return options != null;
}
}
@Override
public void setDebugEnabled(boolean enabled) {
boolean fireChangedEvent = false;
synchronized (lock) {
if (enabled) {
if (options != null)
return;
this.newSession = true;
environmentInfo.setConfiguration(OSGI_DEBUG, "");
if (disabledOptions != null) {
options = disabledOptions;
disabledOptions = null;
fireChangedEvent = true;
} else {
options = new Properties();
}
} else {
if (options == null)
return;
environmentInfo.clearConfiguration(OSGI_DEBUG);
if (options.size() > 0) {
disabledOptions = options;
fireChangedEvent = true;
}
options = null;
}
}
if (fireChangedEvent) {
optionsChanged("*");
}
}
@Override
public final DebugTrace newDebugTrace(String bundleSymbolicName) {
return this.newDebugTrace(bundleSymbolicName, null);
}
@Override
public final DebugTrace newDebugTrace(String bundleSymbolicName, Class<?> traceEntryClass) {
DebugTrace debugTrace = null;
synchronized (debugTraceCache) {
debugTrace = debugTraceCache.get(bundleSymbolicName);
if (debugTrace == null) {
debugTrace = new EclipseDebugTrace(bundleSymbolicName, this, traceEntryClass);
debugTraceCache.put(bundleSymbolicName, debugTrace);
}
}
return debugTrace;
}
@Override
public final File getFile() {
return this.outFile;
}
@Override
public void setFile(final File traceFile) {
synchronized (lock) {
this.outFile = traceFile;
if (this.outFile != null)
environmentInfo.setConfiguration(PROP_TRACEFILE, this.outFile.getAbsolutePath());
else
environmentInfo.clearConfiguration(PROP_TRACEFILE);
this.newSession = true;
}
}
boolean newSession() {
synchronized (lock) {
if (newSession) {
this.newSession = false;
return true;
}
return false;
}
}
Object getWriteLock() {
return writeLock;
}
boolean isVerbose() {
return this.verboseDebug;
}
EquinoxConfiguration getConfiguration() {
return this.environmentInfo;
}
public void setVerbose(final boolean verbose) {
synchronized (lock) {
this.verboseDebug = verbose;
this.newSession = true;
}
}
private void optionsChanged(String bundleSymbolicName) {
BundleContext bc = context;
if (bc == null)
return;
ServiceReference<?>[] listenerRefs = null;
try {
listenerRefs = bc.getServiceReferences(DebugOptionsListener.class.getName(), "(" + DebugOptions.LISTENER_SYMBOLICNAME + "=" + bundleSymbolicName + ")");
} catch (InvalidSyntaxException e) {
}
if (listenerRefs == null)
return;
for (ServiceReference<?> listenerRef : listenerRefs) {
DebugOptionsListener service = (DebugOptionsListener) bc.getService(listenerRef);
if (service == null)
continue;
try {
service.optionsChanged(this);
}catch (Throwable t) {
} finally {
bc.ungetService(listenerRef);
}
}
}
@Override
public DebugOptionsListener addingService(ServiceReference<DebugOptionsListener> reference) {
DebugOptionsListener listener = context.getService(reference);
listener.optionsChanged(this);
return listener;
}
@Override
public void modifiedService(ServiceReference<DebugOptionsListener> reference, DebugOptionsListener service) {
}
@Override
public void removedService(ServiceReference<DebugOptionsListener> reference, DebugOptionsListener service) {
context.ungetService(reference);
}
}