--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.agent;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Each instance of this class represents an entered monitor.
+ *
+ * The reference to the monitor object is kept as a WeakReference internally in
+ * this class and won't prevent the monitor from being garbage collected.
+ *
+ * TODO Add basic test for the WeakReference handling.
+ */
+final class EnteredMonitor {
+ private final WeakReference mMonitorRef;
+ private final int mLockingContextId;
+ private final int mLockId;
+
+ EnteredMonitor(Object monitor,
+ int lockId,
+ int lockingContextId) {
+ mLockId = lockId;
+ mLockingContextId = lockingContextId;
+ mMonitorRef = new WeakReference<Object>(monitor);
+ }
+
+ Object getMonitorIfStillHeld() {
+ final Object monitor = mMonitorRef.get();
+ /*
+ * TODO The call to Thread.holdsLock takes long time. It would be
+ * interesting to test how the performance would be affected if this
+ * code is removed and replaced by an event for each MonitorExit and
+ * each finished synchronized method.
+ */
+ if (monitor != null && Thread.holdsLock(monitor)) {
+ return monitor;
+ } else {
+ return null;
+ }
+ }
+
+ int getLockingContextId() {
+ return mLockingContextId;
+ }
+
+ int getLockId() {
+ return mLockId;
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.agent;
+
+import static com.enea.jcarder.common.contexts.ContextFileReader.EVENT_DB_FILENAME;
+import static com.enea.jcarder.common.contexts.ContextFileReader.CONTEXTS_DB_FILENAME;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Iterator;
+
+//import net.jcip.annotations.ThreadSafe;
+
+import com.enea.jcarder.common.LockingContext;
+import com.enea.jcarder.common.contexts.ContextFileWriter;
+import com.enea.jcarder.common.contexts.ContextWriterIfc;
+import com.enea.jcarder.common.events.EventFileWriter;
+import com.enea.jcarder.common.events.LockEventListenerIfc;
+import com.enea.jcarder.util.Counter;
+//import com.enea.jcarder.util.TransactionalCounter;
+import com.enea.jcarder.util.TransactionalCounter;
+import com.enea.jcarder.util.logging.Logger;
+
+import dstm2.Thread;
+
+//@ThreadSafe
+import java.util.Vector;
+import java.util.concurrent.Callable;
+final class EventListener implements EventListenerIfc {
+ private final ThreadLocalEnteredMonitors mEnteredMonitors;
+ private final LockEventListenerIfc mLockEventListener;
+ private final LockIdGenerator mLockIdGenerator;
+ private final LockingContextIdCache mContextCache;
+ private final Logger mLogger;
+ private final Counter mNumberOfEnteredMonitors;
+
+
+ private final TransactionalCounter trmNumberOfEnteredMonitors;
+
+ public static EventListener create(Logger logger, File outputdir)
+ throws IOException {
+ EventFileWriter eventWriter =
+ new EventFileWriter(logger,
+ new File(outputdir, EVENT_DB_FILENAME));
+ ContextFileWriter contextWriter =
+ new ContextFileWriter(logger,
+ new File(outputdir, CONTEXTS_DB_FILENAME));
+ return new EventListener(logger, eventWriter, contextWriter);
+ }
+
+ public EventListener(Logger logger,
+ LockEventListenerIfc lockEventListener,
+ ContextWriterIfc contextWriter) {
+ mLogger = logger;
+ mEnteredMonitors = new ThreadLocalEnteredMonitors();
+ mLockEventListener = lockEventListener;
+ mLockIdGenerator = new LockIdGenerator(mLogger, contextWriter);
+ mContextCache = new LockingContextIdCache(mLogger, contextWriter);
+ mNumberOfEnteredMonitors =
+ new Counter("Entered Monitors", mLogger, 100000);
+
+ trmNumberOfEnteredMonitors =
+ new TransactionalCounter("Entered Monitors", mLogger, 100000);
+ }
+
+ public void beforeMonitorEnter(Object monitor, LockingContext context)
+ throws Exception {
+ mLogger.finest("EventListener.beforeMonitorEnter");
+ Iterator<EnteredMonitor> iter = mEnteredMonitors.getIterator();
+ while (iter.hasNext()) {
+ Object previousEnteredMonitor = iter.next().getMonitorIfStillHeld();
+ if (previousEnteredMonitor == null) {
+ iter.remove();
+ } else if (previousEnteredMonitor == monitor) {
+ return; // Monitor already entered.
+ }
+ }
+ //enteringNewMonitor(monitor, context);
+ enteringNewMonitor(monitor, context);
+
+ }
+
+ private synchronized void enteringNewMonitor(Object monitor,
+ LockingContext context)
+ throws Exception {
+ mNumberOfEnteredMonitors.increment();
+ int newLockId = mLockIdGenerator.acquireLockId(monitor);
+ int newContextId = mContextCache.acquireContextId(context);
+
+ EnteredMonitor lastMonitor = mEnteredMonitors.getFirst();
+ if (lastMonitor != null) {
+ java.lang.Thread performingThread = Thread.currentThread();
+ mLockEventListener.onLockEvent(newLockId,
+ newContextId,
+ lastMonitor.getLockId(),
+ lastMonitor.getLockingContextId(),
+ performingThread.getId());
+ }
+ mEnteredMonitors.addFirst(new EnteredMonitor(monitor,
+ newLockId,
+ newContextId));
+
+ }
+
+ private synchronized void trenteringNewMonitor(Object monitor,
+ Vector context)
+ throws Exception {
+ mNumberOfEnteredMonitors.increment();
+ int newLockId = mLockIdGenerator.acquireLockId(monitor);
+ int newContextId = mContextCache.acquireContextId((LockingContext)context.get(0));
+
+ EnteredMonitor lastMonitor = mEnteredMonitors.getFirst();
+ if (lastMonitor != null) {
+ java.lang.Thread performingThread = Thread.currentThread();
+ Vector args = new Vector();
+ args.add(newLockId);
+ args.add(newContextId);
+ args.add(lastMonitor.getLockId());
+ args.add(lastMonitor.getLockingContextId());
+ args.add(performingThread.getId());
+
+ mLockEventListener.tronLockEvent(args);
+ }
+ mEnteredMonitors.addFirst(new EnteredMonitor(monitor,
+ newLockId,
+ newContextId));
+
+ }
+
+ private void trenteringNewMonitorWrapper(Object monitor,
+ LockingContext context)
+ throws Exception {
+ final Vector arguments = new Vector();
+ arguments.add(context);
+ Thread.doIt(new Callable<Boolean>() {
+ public Boolean call() throws Exception {
+ trenteringNewMonitor(mLogger, arguments);
+ return true;
+ }
+ });
+
+
+
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.agent;
+
+import com.enea.jcarder.common.LockingContext;
+
+public interface EventListenerIfc {
+
+ void beforeMonitorEnter(Object monitor,
+ LockingContext context) throws Exception;
+
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.agent;
+
+import TransactionalIO.core.TransactionalFile;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.instrument.Instrumentation;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+
+import com.enea.jcarder.agent.instrument.ClassTransformer;
+import com.enea.jcarder.agent.instrument.InstrumentConfig;
+import com.enea.jcarder.util.BuildInformation;
+import com.enea.jcarder.util.logging.AppendableHandler;
+import com.enea.jcarder.util.logging.Handler;
+import com.enea.jcarder.util.logging.Logger;
+import dstm2.Init;
+import java.io.RandomAccessFile;
+
+/**
+ * This is the main class of the JCarder Java agent. It will initialize JCarder
+ * and register a ClassTransformer that is called by the JVM each time a class
+ * is loaded.
+ */
+public final class JavaAgent {
+
+ private static final String DUMP_PROPERTY = "jcarder.dump";
+ private static final String LOGLEVEL_PROPERTY = "jcarder.loglevel";
+ private static final String LOG_FILENAME = "jcarder.log";
+ private static final String LOG2_FILENAME = "jcarder2ndlog.log";
+
+ private final InstrumentConfig mConfig = new InstrumentConfig();
+ private Logger mLogger;
+ PrintWriter mLogWriter;
+ private File mOutputDir;
+ private Logger.Level mLogLevel;
+ private static final String OUTPUTDIR_PROPERTY = "jcarder.outputdir";
+
+ private JavaAgent() { }
+
+ /**
+ * This method is called by the JVM when the JVM is started with the
+ * -javaagent command line parameter.
+ */
+ public static void premain(final String args,
+ final Instrumentation instrumentation)
+ throws Exception {
+ Init.init();
+ JavaAgent javaAgent = new JavaAgent();
+ javaAgent.init(instrumentation);
+ }
+
+ private void init(Instrumentation instrumentation)
+ throws Exception {
+ handleProperties();
+ initLogger();
+ mLogger.info("Starting " + BuildInformation.getShortInfo() + " agent");
+ logJvmInfo();
+ EventListener listener = EventListener.create(mLogger, mOutputDir);
+ ClassTransformer classTransformer =
+ new ClassTransformer(mLogger, mOutputDir, mConfig);
+ instrumentation.addTransformer(classTransformer);
+ StaticEventListener.setListener(listener);
+ mLogger.info("JCarder agent initialized\n");
+ }
+
+ private void initLogger() {
+ File logFile = new File(mOutputDir, LOG_FILENAME);
+ if (logFile.exists()) {
+ logFile.delete();
+ }
+ //TransactionalFile traf = new TransactionalFile(logFile, "rw");
+ FileWriter fileWriter;
+ try {
+ fileWriter = new FileWriter(logFile);
+ } catch (IOException e) {
+ System.err.println("Failed to open log file \""
+ + logFile + "\": " + e.getMessage());
+ return;
+ }
+ mLogWriter = new PrintWriter(new BufferedWriter(fileWriter));
+ AppendableHandler fileHandler = new AppendableHandler(mLogWriter);
+ // AppendableHandler fileHandler = new AppendableHandler(mLogWriter, new TransactionalFile(LOG_FILENAME, "rw"));
+ AppendableHandler consoleHandler =
+ new AppendableHandler(System.err, Logger.Level.INFO, "{message}\n");
+
+ /* File logFile2 = new File(mOutputDir, LOG2_FILENAME);
+ if (logFile2.exists()) {
+ logFile2.delete();
+ }
+ //TransactionalFile traf = new TransactionalFile(logFile, "rw");
+ FileWriter fileWriter2;
+ try {
+ fileWriter2 = new FileWriter(logFile2);
+ } catch (IOException e) {
+ System.err.println("Failed to open log file \""
+ + logFile2 + "\": " + e.getMessage());
+ return;
+ }
+
+
+ AppendableHandler consoleHandler =
+ new AppendableHandler(new PrintWriter(new BufferedWriter(fileWriter)), Logger.Level.INFO, "{message}\n", new TransactionalFile(LOG2_FILENAME, "rw"));
+*/
+ Thread hook = new Thread() {
+ public void run() {
+ mLogWriter.flush();
+ }
+ };
+ Runtime.getRuntime().addShutdownHook(hook);
+
+ Collection<Handler> handlers = new ArrayList<Handler>();
+ handlers.add(fileHandler);
+ handlers.add(consoleHandler);
+ mLogger = new Logger(handlers, mLogLevel);
+ }
+
+ private void logJvmInfo() {
+ Enumeration<?> properties = System.getProperties().propertyNames();
+ while (properties.hasMoreElements()) {
+ String key = (String) properties.nextElement();
+ if (key.startsWith("java.vm.")) {
+ mLogger.config(key + ": " + System.getProperty(key));
+ }
+ }
+ }
+
+ private void handleProperties() throws IOException {
+ handleDumpProperty();
+ handleLogLevelProperty();
+ handleOutputDirProperty();
+ }
+
+ private void handleDumpProperty() {
+ mConfig.setDumpClassFiles(Boolean.getBoolean(DUMP_PROPERTY));
+ }
+
+ private void handleLogLevelProperty() {
+ String logLevelValue = System.getProperty(LOGLEVEL_PROPERTY, "fine");
+ Logger.Level logLevel = Logger.Level.fromString(logLevelValue);
+ if (logLevel != null) {
+ mLogLevel = logLevel;
+ } else {
+ System.err.print("Bad loglevel; should be one of ");
+ System.err.println(Logger.Level.getEnumeration());
+ System.err.println();
+ System.exit(1);
+ }
+ }
+
+ private void handleOutputDirProperty() throws IOException {
+ final String property = System.getProperty(OUTPUTDIR_PROPERTY, ".");
+ mOutputDir = new File(property).getCanonicalFile();
+ if (!mOutputDir.isDirectory()) {
+ mOutputDir.mkdirs();
+ }
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.agent;
+
+import java.io.IOException;
+//import net.jcip.annotations.NotThreadSafe;
+
+import com.enea.jcarder.common.Lock;
+import com.enea.jcarder.common.contexts.ContextWriterIfc;
+import com.enea.jcarder.util.IdentityWeakHashMap;
+import com.enea.jcarder.util.logging.Logger;
+
+/**
+ * This class is responsible for generating unique IDs for objects.
+ *
+ * We cannot use System.identityHashCode(o) since it returns random numbers,
+ * which are not guaranteed to be unique.
+ *
+ * TODO Add basic tests for this class.
+ */
+//@NotThreadSafe
+final class LockIdGenerator {
+ private final IdentityWeakHashMap<Integer> mIdMap;
+ private final ContextWriterIfc mContextWriter;
+ private final Logger mLogger;
+
+ /**
+ * Create a LockIdGenerator backed by a ContextWriterIfc
+ */
+ public LockIdGenerator(Logger logger, ContextWriterIfc writer) {
+ mLogger = logger;
+ mIdMap = new IdentityWeakHashMap<Integer>();
+ mContextWriter = writer;
+ }
+
+ /**
+ * Return an ID for a given object.
+ *
+ * If the method is invoked with the same object instance more than once it
+ * is guaranteed that the same ID is returned each time. Two objects that
+ * are not identical (as compared with "==") will get different IDs.
+ */
+ public int acquireLockId(Object o) throws IOException {
+ assert o != null;
+ Integer id = mIdMap.get(o);
+ if (id == null) {
+ id = mContextWriter.writeLock(new Lock(o));
+ mIdMap.put(o, id);
+ mLogger.finest("Created new lock ID: " + id);
+ }
+ return id;
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.agent;
+
+import java.io.IOException;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.util.HashMap;
+//import net.jcip.annotations.NotThreadSafe;
+
+import com.enea.jcarder.common.LockingContext;
+import com.enea.jcarder.common.contexts.ContextWriterIfc;
+import com.enea.jcarder.util.logging.Logger;
+
+/**
+ * This class is responsible for mapping LockingContext instances to locking
+ * context IDs. It maintains a cache to be able to return the same ID again if
+ * an ID is requested for the same or equal LockingContext more than once.
+ *
+ * This class is similar to the java.util.WeakHashMap but uses soft references
+ * instead of weak references in order to try to keep the entries in the cache
+ * as long as there is enough memory available.
+ *
+ * TODO An alternative implementation to consider could be to only store the
+ * hashCode in a map and perform a comparison with the Context file file
+ * (possibly memory mapped). I don't know how that would affect the performance.
+ * Another option to consider would be to use a plain HashMap without
+ * SoftReferences and accept the potential memory problem as a trade-of for
+ * better performance (?) and to avoid getting different IDs for duplicated
+ * LockingContexts.
+ *
+ * TODO Add basic tests for this class.
+ */
+//@NotThreadSafe
+final class LockingContextIdCache {
+ private final HashMap<EqualsComparableKey, Integer> mCache;
+ private final ReferenceQueue<Object> mReferenceQueue;
+ private final ContextWriterIfc mContextWriter;
+ private final Logger mLogger;
+
+ /**
+ * Create a LockingContextIdCache backed by a ContextWriterIfc.
+ */
+ public LockingContextIdCache(Logger logger, ContextWriterIfc writer) {
+ mLogger = logger;
+ mCache = new HashMap<EqualsComparableKey, Integer>();
+ mReferenceQueue = new ReferenceQueue<Object>();
+ mContextWriter = writer;
+ }
+
+ /**
+ * Acquire a unique ID for the provided LockingContext. The ID will be
+ * cached. If a provided LockingContext is equal to a previously provided
+ * LockingContext that is still in the cache, the same ID will be returned.
+ *
+ * The equality is checked with the LockingContext.equals(Object other)
+ * method.
+ */
+ public int acquireContextId(LockingContext context) throws IOException {
+ assert context != null;
+ removeGarbageCollectedKeys();
+ Integer id = mCache.get(new StrongKey(context));
+ if (id == null) {
+ mLogger.finest("Creating new context ID");
+ id = mContextWriter.writeContext(context);
+ mCache.put((new SoftKey(context, mReferenceQueue)), id);
+ }
+ return id;
+ }
+
+ private void removeGarbageCollectedKeys() {
+ Reference e;
+ while ((e = mReferenceQueue.poll()) != null) {
+ mLogger.finest("Removing garbage-collected cached context");
+ mCache.remove(e);
+ }
+ }
+
+ private static interface EqualsComparableKey {
+ Object get();
+ boolean equals(Object obj);
+ int hashCode();
+ }
+
+ private static class StrongKey implements EqualsComparableKey {
+ private final Object mReferent;
+
+ StrongKey(Object referent) {
+ assert referent != null;
+ mReferent = referent;
+ }
+
+ public Object get() {
+ return mReferent;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ try {
+ EqualsComparableKey reference = (EqualsComparableKey) obj;
+ return mReferent.equals(reference.get());
+ } catch (ClassCastException e) {
+ return false;
+ }
+ }
+
+ public int hashCode() {
+ return mReferent.hashCode();
+ }
+ }
+
+ private static class SoftKey extends SoftReference<LockingContext>
+ implements EqualsComparableKey {
+ private final int mHash;
+
+ SoftKey(LockingContext referent, ReferenceQueue<Object> queue) {
+ super(referent, queue);
+ assert referent != null;
+ mHash = referent.hashCode();
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ try {
+ Object otherReferent = ((EqualsComparableKey) obj).get();
+ Object thisReferent = get();
+ return (thisReferent != null
+ && otherReferent != null
+ && thisReferent.equals(otherReferent));
+ } catch (ClassCastException e) {
+ return false;
+ }
+ }
+
+ public int hashCode() {
+ return mHash;
+ }
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.agent;
+
+//import net.jcip.annotations.ThreadSafe;
+import com.enea.jcarder.common.LockingContext;
+
+/**
+ * This class provides static methods that are supposed to be invoked directly
+ * from the instrumented classes.
+ */
+//@ThreadSafe
+public final class StaticEventListener {
+
+ private StaticEventListener() { }
+ private static EventListenerIfc smListener;
+
+ public synchronized static void setListener(EventListenerIfc listener) {
+ smListener = listener;
+ }
+
+ public synchronized static EventListenerIfc getListener() {
+ return smListener;
+ }
+
+ /**
+ * This method is expected to be called from the instrumented classes.
+ *
+ * @param monitor
+ * The monitor object that was acquired. This value is allowed to
+ * be null.
+ *
+ * @param lockReference
+ * A textual description of how the lock object was addressed.
+ * For example: "this", "com.enea.jcarder.Foo.mBar" or
+ * "com.enea.jcarder.Foo.getLock()".
+ *
+ * @param methodWithClass
+ * The method that acquired the lock, on the format
+ * "com.enea.jcarder.Foo.bar()".
+ */
+ public static void beforeMonitorEnter(Object monitor,
+ String lockReference,
+ String methodWithClass) {
+ try {
+ EventListenerIfc listener = getListener();
+ if (listener != null) {
+ final LockingContext lockingContext =
+ new LockingContext(Thread.currentThread(),
+ lockReference,
+ methodWithClass);
+ listener.beforeMonitorEnter(monitor,
+ lockingContext);
+ }
+ } catch (Throwable t) {
+ handleError(t);
+ }
+ }
+
+ private static void handleError(Throwable t) {
+ setListener(null);
+ t.printStackTrace();
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.agent;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+//import net.jcip.annotations.ThreadSafe;
+
+/**
+ * Each instance of this class keeps a list of entered monitors for a thread.
+ *
+ * Note that this class is a ThreadLocal and therefore each thread will have its
+ * own instance.
+ */
+
+//@ThreadSafe
+final class ThreadLocalEnteredMonitors
+extends ThreadLocal<ArrayList<EnteredMonitor>> {
+
+ public ArrayList<EnteredMonitor> initialValue() {
+ return new ArrayList<EnteredMonitor>();
+ }
+
+ Iterator<EnteredMonitor> getIterator() {
+ return get().iterator();
+ }
+
+ EnteredMonitor getFirst() {
+ ArrayList<EnteredMonitor> list = get();
+ if (list.isEmpty()) {
+ return null;
+ } else {
+ return list.get(0);
+ }
+ }
+
+ void addFirst(EnteredMonitor enteredMonitor) {
+ get().add(0, enteredMonitor);
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.agent.instrument;
+
+import com.enea.jcarder.org.objectweb.asm.ClassReader;
+import com.enea.jcarder.org.objectweb.asm.ClassVisitor;
+import com.enea.jcarder.org.objectweb.asm.ClassWriter;
+import com.enea.jcarder.org.objectweb.asm.util.CheckClassAdapter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.IllegalClassFormatException;
+import java.security.ProtectionDomain;
+
+
+import com.enea.jcarder.util.logging.Logger;
+
+/**
+ * This class is responsible for all instrumentations and handles related issues
+ * with class loaders.
+ *
+ * TODO Add basic test for this class.
+ */
+public class ClassTransformer implements ClassFileTransformer {
+ private static final String ORIGINAL_CLASSES_DIRNAME =
+ "jcarder_original_classes";
+ private static final String INSTRUMENTED_CLASSES_DIRNAME =
+ "jcarder_instrumented_classes";
+ private final Logger mLogger;
+ private final ClassLoader mAgentClassLoader;
+ private final InstrumentConfig mInstrumentConfig;
+ private File mOriginalClassesDir;
+ private File mInstrumentedClassesDir;
+
+ public ClassTransformer(Logger logger,
+ File outputDirectory,
+ InstrumentConfig config) {
+ mLogger = logger;
+ mOriginalClassesDir =
+ new File(outputDirectory, ORIGINAL_CLASSES_DIRNAME);
+ mInstrumentedClassesDir =
+ new File(outputDirectory, INSTRUMENTED_CLASSES_DIRNAME);
+ mInstrumentConfig = config;
+ mAgentClassLoader = getClass().getClassLoader();
+ mLogger.fine("JCarder loaded with "
+ + getClassLoaderName(mAgentClassLoader) + ".");
+ if (mAgentClassLoader == null) {
+ mLogger.info("Will instrument AWT and Swing classes");
+ } else {
+ mLogger.info("Not instrumenting standard library classes "
+ + "(AWT, Swing, etc.)");
+ }
+ deleteDirRecursively(mInstrumentedClassesDir);
+ deleteDirRecursively(mOriginalClassesDir);
+ }
+
+ public byte[] transform(final ClassLoader classLoader,
+ final String jvmInternalClassName,
+ final Class<?> classBeingRedefined,
+ final ProtectionDomain protectionDomain,
+ final byte[] originalClassBuffer)
+ throws IllegalClassFormatException {
+ String className = jvmInternalClassName.replace('/', '.');
+ try {
+ return instrument(classLoader, originalClassBuffer, className);
+ } catch (Throwable t) {
+ mLogger.severe("Failed to transform the class "
+ + className + ": " + t.getMessage());
+ dumpClassToFile(originalClassBuffer,
+ mOriginalClassesDir,
+ className);
+ return null;
+ }
+ }
+
+ private byte[] instrument(final ClassLoader classLoader,
+ final byte[] originalClassBuffer,
+ final String className) {
+ if (className.startsWith("com.enea.jcarder")
+ && !className.startsWith("com.enea.jcarder.testclasses")) {
+ return null; // Don't instrument ourself.
+ }
+ final String reason = isInstrumentable(className);
+ if (reason != null) {
+ mLogger.finest(
+ "Won't instrument class " + className + ": " + reason);
+ return null;
+ }
+ if (!isCompatibleClassLoader(classLoader)) {
+ mLogger.finest("Can't instrument class " + className
+ + " loaded with " + getClassLoaderName(classLoader));
+ return null;
+ }
+ final ClassReader reader = new ClassReader(originalClassBuffer);
+ final ClassWriter writer = new ClassWriter(true);
+ ClassVisitor visitor = writer;
+ if (mInstrumentConfig.getValidateTransfomedClasses()) {
+ visitor = new CheckClassAdapter(visitor);
+ }
+ visitor = new ClassAdapter(mLogger, visitor, className);
+ reader.accept(visitor, false);
+ byte[] instrumentedClassfileBuffer = writer.toByteArray();
+ if (mInstrumentConfig.getDumpClassFiles()) {
+ dumpClassToFile(originalClassBuffer,
+ mOriginalClassesDir,
+ className);
+ dumpClassToFile(instrumentedClassfileBuffer,
+ mInstrumentedClassesDir,
+ className);
+ }
+ return instrumentedClassfileBuffer;
+ }
+
+ /**
+ * Instrumented classes must use the same static members in the
+ * com.ena.jcarder.agent.StaticEventListener class as the Java agent and
+ * therefore they must be loaded with the same class loader as the agent was
+ * loaded with, or with a class loader that has the agent's class loader as
+ * a parent or ancestor.
+ *
+ * Note that the agentLoader may have been loaded with the bootstrap class
+ * loader (null) and then "null" is a compatible class loader.
+ */
+ private boolean isCompatibleClassLoader(final ClassLoader classLoader) {
+ ClassLoader c = classLoader;
+ while (c != mAgentClassLoader) {
+ if (c == null) {
+ return false;
+ }
+ c = c.getParent();
+ }
+ return true;
+ }
+
+ private static String getClassLoaderName(final ClassLoader loader) {
+ if (loader == null) {
+ return "the bootstrap class loader";
+ } else if (loader == ClassLoader.getSystemClassLoader()) {
+ return "the system class loader";
+ } else {
+ return "the class loader \"" + loader + "\"";
+ }
+ }
+
+ /**
+ * Check whether we want to instrument a class.
+ *
+ * @param className Name of the class, including package.
+ * @return null if the class should be instrumented, otherwise a
+ * string containing the reason of why the class shouldn't be
+ * instrumented.
+ */
+ private static String isInstrumentable(String className) {
+ // AWK and Swing classes are OK.
+ if (className.startsWith("java.awt.")
+ || className.startsWith("javax.swing.")) {
+ return null;
+ }
+
+ // Other standard library classes are not.
+ if (className.startsWith("java.")
+ || className.startsWith("javax.")
+ || className.startsWith("sun.") || className.startsWith("TransactionalIO.")
+ || className.startsWith("dstm2")) {
+ return "standard library class";
+ }
+
+ // All other classes should be instrumented.
+ return null;
+ }
+
+ private static boolean deleteDirRecursively(File dir) {
+ if (dir.isDirectory()) {
+ String[] children = dir.list();
+ for (int i = 0; i < children.length; i++) {
+ boolean success =
+ deleteDirRecursively(new File(dir, children[i]));
+ if (!success) {
+ return false;
+ }
+ }
+ }
+ return dir.delete();
+ }
+
+ /**
+ * The dumped file can be decompiled with javap or with gnu.bytecode.dump.
+ * The latter also prints detailed information about the constant pool,
+ * something which javap does not.
+ */
+ private void dumpClassToFile(byte[] content,
+ File baseDir,
+ String className) {
+ try {
+ String separator = System.getProperty("file.separator");
+ File file = new File(baseDir + separator
+ + className.replace(".", separator)
+ + ".class");
+ file.getParentFile().mkdirs();
+ FileOutputStream fos = new FileOutputStream(file);
+ fos.write(content);
+ fos.close();
+ } catch (IOException e) {
+ mLogger.severe("Failed to dump class to file: " + e.getMessage());
+ }
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.agent.instrument;
+
+// TODO Is this config class needed?
+public final class InstrumentConfig {
+
+ private final boolean mValidateTransfomedClasses = true;
+ private boolean mDumpClassFiles;
+
+ public InstrumentConfig() {
+ mDumpClassFiles = false;
+ }
+
+ public void setDumpClassFiles(boolean dumpClassFiles) {
+ mDumpClassFiles = dumpClassFiles;
+ }
+
+ public boolean getDumpClassFiles() {
+ return mDumpClassFiles;
+ }
+
+ public boolean getValidateTransfomedClasses() {
+ return mValidateTransfomedClasses;
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.agent.instrument;
+
+import com.enea.jcarder.org.objectweb.asm.MethodVisitor;
+import com.enea.jcarder.org.objectweb.asm.Opcodes;
+
+
+public final class InstrumentationUtilities {
+
+ private InstrumentationUtilities() { }
+
+ public static void pushClassReferenceToStack(MethodVisitor mv,
+ String className) {
+ /*
+ * It is not possible to use:
+ *
+ * mv.visitLdcInsn(RuleType.getType(mClassName));
+ *
+ * for class versions before 49.0 (introduced with java 1.5). Therefore
+ * we use Class.forName instead.
+ *
+ * TODO It might be possible to do this more efficiently by caching the
+ * result from Class.forName. But note that adding a new field (where
+ * the cached class object can be stored) is only possible if the class
+ * has not already been loaded by the JVM.
+ */
+ mv.visitLdcInsn(className);
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "java/lang/Class",
+ "forName",
+ "(Ljava/lang/String;)Ljava/lang/Class;");
+ }
+
+ public static String getInternalName(Class c) {
+ return c.getName().replace('.', '/');
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.agent.instrument;
+
+import com.enea.jcarder.org.objectweb.asm.Attribute;
+import com.enea.jcarder.org.objectweb.asm.ByteVector;
+import com.enea.jcarder.org.objectweb.asm.ClassWriter;
+
+
+public final class InstrumentedAttribute extends Attribute {
+ private static final String PREFIX = "com.enea.jcarder.instrumented";
+
+ public InstrumentedAttribute() {
+ super(PREFIX);
+ }
+
+ public InstrumentedAttribute(String attributeType) {
+ super(PREFIX + "." + attributeType);
+ }
+
+ public static boolean matchAttribute(Attribute a) {
+ return a.type.startsWith(PREFIX);
+ }
+
+ protected ByteVector write(ClassWriter arg0,
+ byte[] arg1,
+ int arg2,
+ int arg3,
+ int arg4) {
+ return new ByteVector();
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.agent.instrument;
+
+import net.jcip.annotations.NotThreadSafe;
+
+
+import com.enea.jcarder.agent.StaticEventListener;
+
+import com.enea.jcarder.org.objectweb.asm.MethodAdapter;
+import com.enea.jcarder.org.objectweb.asm.MethodVisitor;
+import com.enea.jcarder.org.objectweb.asm.Opcodes;
+import static com.enea.jcarder.agent.instrument.InstrumentationUtilities.getInternalName;
+
+@NotThreadSafe
+class MonitorEnterMethodAdapter extends MethodAdapter {
+ private static final String CALLBACK_CLASS_NAME =
+ getInternalName(StaticEventListener.class);
+ private final String mClassAndMethodName;
+ private final String mClassName;
+ private StackAnalyzeMethodVisitor mStack;
+
+ MonitorEnterMethodAdapter(final MethodVisitor visitor,
+ final String className,
+ final String methodName) {
+ super(visitor);
+ mClassAndMethodName = className + "." + methodName + "()";
+ mClassName = className;
+ }
+
+ public void visitInsn(int inst) {
+ if (inst == Opcodes.MONITORENTER) {
+ mv.visitInsn(Opcodes.DUP);
+ mv.visitLdcInsn(convertFromJvmInternalNames(mStack.peek()));
+ mv.visitLdcInsn(mClassAndMethodName);
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC,
+ CALLBACK_CLASS_NAME,
+ "beforeMonitorEnter",
+ "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V");
+ }
+ super.visitInsn(inst);
+ }
+
+ private String convertFromJvmInternalNames(String s) {
+ if (s == null) {
+ assert false;
+ return "null???";
+ } else {
+ final String name = s.replace('/', '.');
+ if (name.equals(mClassName + ".class")) {
+ return "class";
+ } else {
+ return name;
+ }
+ }
+ }
+
+ void setStackAnalyzer(StackAnalyzeMethodVisitor stack) {
+ mStack = stack;
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.agent.instrument;
+
+import com.enea.jcarder.org.objectweb.asm.Label;
+import com.enea.jcarder.org.objectweb.asm.MethodAdapter;
+import com.enea.jcarder.org.objectweb.asm.MethodVisitor;
+import com.enea.jcarder.org.objectweb.asm.Opcodes;
+import net.jcip.annotations.NotThreadSafe;
+
+/**
+ * This Method Adapter simulates a synchronized declaration on a method by
+ * adding a MonitorEnter and MonitorExits.
+ */
+//@NotThreadSafe
+class SimulateMethodSyncMethodAdapter extends MethodAdapter {
+ private final String mClassName;
+ private final boolean mIsStatic;
+ private final Label mTryLabel = new Label();
+ private final Label mFinallyLabel = new Label();
+
+ SimulateMethodSyncMethodAdapter(final MethodVisitor visitor,
+ final String className,
+ final boolean isStatic) {
+ super(visitor);
+ mClassName = className;
+ mIsStatic = isStatic;
+ }
+
+ public void visitCode() {
+ super.visitCode();
+ /*
+ * This MethodAdapter will only be applied to synchronized methods, and
+ * constructors are not allowed to be declared synchronized. Therefore
+ * we can add instructions at the beginning of the method and do not
+ * have to find the place after the initial constructor byte codes:
+ *
+ * ALOAD 0 : this
+ * INVOKESPECIAL Object.<init>() : void
+ *
+ */
+ putMonitorObjectReferenceOnStack();
+ mv.visitInsn(Opcodes.MONITORENTER);
+ mv.visitLabel(mTryLabel);
+ }
+
+ /**
+ * This method is called just after the last code in the method.
+ */
+ public void visitMaxs(int arg0, int arg1) {
+ /*
+ * This finally block is needed in order to exit the monitor even when
+ * the method exits by throwing an exception.
+ */
+ mv.visitLabel(mFinallyLabel);
+ putMonitorObjectReferenceOnStack();
+ mv.visitInsn(Opcodes.MONITOREXIT);
+ mv.visitInsn(Opcodes.ATHROW);
+ mv.visitTryCatchBlock(mTryLabel,
+ mFinallyLabel,
+ mFinallyLabel,
+ null);
+ super.visitMaxs(arg0, arg1);
+ }
+
+ public void visitInsn(int inst) {
+ switch (inst) {
+ case Opcodes.IRETURN:
+ case Opcodes.LRETURN:
+ case Opcodes.FRETURN:
+ case Opcodes.DRETURN:
+ case Opcodes.ARETURN:
+ case Opcodes.RETURN:
+ putMonitorObjectReferenceOnStack();
+ mv.visitInsn(Opcodes.MONITOREXIT);
+ break;
+ default:
+ // Do nothing.
+ }
+ super.visitInsn(inst);
+ }
+
+ private void putMonitorObjectReferenceOnStack() {
+ if (mIsStatic) {
+ InstrumentationUtilities.pushClassReferenceToStack(mv, mClassName);
+ } else {
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ }
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.agent.instrument;
+
+import com.enea.jcarder.org.objectweb.asm.AnnotationVisitor;
+import com.enea.jcarder.org.objectweb.asm.Attribute;
+import com.enea.jcarder.org.objectweb.asm.Label;
+import com.enea.jcarder.org.objectweb.asm.MethodVisitor;
+import com.enea.jcarder.org.objectweb.asm.Opcodes;
+import com.enea.jcarder.org.objectweb.asm.Type;
+import java.util.Stack;
+import net.jcip.annotations.NotThreadSafe;
+
+
+
+import com.enea.jcarder.util.logging.Logger;
+
+/**
+ * This class tries to keep track of what is currently on the operand stack. It
+ * does not keep track of the actual values but from where the values
+ * originates. A value may for example originate from a specific field member in
+ * the class, a local variable, a return value from a specific method or
+ * something else.
+ *
+ * The analysis is done during the instrumentation.
+ */
+@NotThreadSafe
+class StackAnalyzeMethodVisitor implements MethodVisitor {
+ private static final TextualDescription UNKOWN_VALUE =
+ new TextualDescription("???");
+ private final Logger mLogger;
+ private final Stack<Object> mStack = new Stack<Object>();
+ private final MethodVisitor mMethodVisitor;
+ private final boolean mIsStatic;
+
+ StackAnalyzeMethodVisitor(final Logger logger,
+ final MethodVisitor methodVisitor,
+ final boolean isStatic) {
+ mLogger = logger;
+ mMethodVisitor = methodVisitor;
+ mIsStatic = isStatic;
+ }
+
+ private static class TextualDescription {
+ private final String mDescription;
+
+ TextualDescription(String description) {
+ mDescription = description;
+ }
+
+ public String toString() {
+ return mDescription;
+ }
+ }
+
+ /**
+ * @return A textual description of from where the current value of the
+ * stack originates. The string "???" is returned if the origin is
+ * unknown.
+ */
+ String peek() {
+ if (mStack.isEmpty()) {
+ return UNKOWN_VALUE.toString();
+ } else {
+ return mStack.peek().toString();
+ }
+ }
+
+ private String pop() {
+ return popObject().toString();
+ }
+
+ private Object popObject() {
+ if (mStack.isEmpty()) {
+ return UNKOWN_VALUE;
+ } else {
+ return mStack.pop();
+ }
+ }
+
+ private void pushTextualDescription(String s) {
+ mStack.push(new TextualDescription(s));
+ }
+
+ private void pushStringObject(String s) {
+ mStack.push(s);
+ }
+
+ private void clear() {
+ mLogger.finest("Invalidating stack");
+ mStack.clear();
+ }
+
+ public void visitCode() {
+ mMethodVisitor.visitCode();
+ clear();
+ }
+
+ public void visitEnd() {
+ mMethodVisitor.visitEnd();
+ clear();
+ }
+
+ public void visitFieldInsn(int opCode,
+ String owner,
+ String name,
+ String desc) {
+ mMethodVisitor.visitFieldInsn(opCode, owner, name, desc);
+ switch (opCode) {
+ case Opcodes.GETFIELD:
+ pop();
+ pushTextualDescription(owner + "." + name);
+ break;
+ case Opcodes.GETSTATIC:
+ pushTextualDescription(owner + "." + name);
+ break;
+ default:
+ clear();
+ }
+ }
+
+ public void visitIincInsn(int arg0, int arg1) {
+ mMethodVisitor.visitIincInsn(arg0, arg1);
+ clear();
+ }
+
+ public void visitInsn(int opCode) {
+ mMethodVisitor.visitInsn(opCode);
+ switch (opCode) {
+ case Opcodes.DUP:
+ pushTextualDescription(peek());
+ break;
+ default:
+ clear();
+ }
+ }
+
+ public void visitIntInsn(int opCode, int arg1) {
+ mMethodVisitor.visitIntInsn(opCode, arg1);
+ clear();
+ }
+
+ public void visitJumpInsn(int opCode, Label arg1) {
+ mMethodVisitor.visitJumpInsn(opCode, arg1);
+ clear();
+ }
+
+ public void visitLabel(Label arg0) {
+ mMethodVisitor.visitLabel(arg0);
+ // We have to invalidate the stack since we don't know how we arrived
+ // at this label. We might have jumped to this place from anywhere.
+ clear();
+ }
+
+ public void visitLdcInsn(Object cst) {
+ mMethodVisitor.visitLdcInsn(cst);
+ if (cst instanceof Type) {
+ Type t = (Type) cst;
+ pushTextualDescription(t.getClassName() + ".class");
+ } else if (cst instanceof String) {
+ pushStringObject((String) cst);
+ } else {
+ clear();
+ }
+ }
+
+ public void visitLineNumber(int arg0, Label arg1) {
+ mMethodVisitor.visitLineNumber(arg0, arg1);
+ }
+
+ public void visitLocalVariable(String name,
+ String desc,
+ String signature,
+ Label start,
+ Label end,
+ int index) {
+ mMethodVisitor.visitLocalVariable(name,
+ desc,
+ signature,
+ start,
+ end,
+ index);
+ }
+
+ public void visitLookupSwitchInsn(Label arg0, int[] arg1, Label[] arg2) {
+ mMethodVisitor.visitLookupSwitchInsn(arg0, arg1, arg2);
+ clear();
+ }
+
+ // TODO refactor this method
+ public void visitMethodInsn(int opCode,
+ String owner,
+ String name,
+ String desc) {
+ mMethodVisitor.visitMethodInsn(opCode, owner, name, desc);
+ switch (opCode) {
+ case Opcodes.INVOKEVIRTUAL:
+ // pass through to next case.
+ case Opcodes.INVOKESPECIAL:
+ // pass through to next case.
+ case Opcodes.INVOKESTATIC:
+ if ("forName".equals(name)
+ && "java/lang/Class".equals(owner)
+ && "(Ljava/lang/String;)Ljava/lang/Class;".equals(desc)) {
+ Object stackObject = popObject();
+ if (stackObject instanceof String) {
+ String classDescription = ((String) stackObject) + ".class";
+ pushTextualDescription(classDescription);
+ break;
+ }
+ }
+ // pass through to next case.
+ case Opcodes.INVOKEINTERFACE:
+ clear();
+ if (isNonVoidMethod(name, desc)) {
+ pushTextualDescription(owner + "." + name + "()");
+ }
+ break;
+ default:
+ clear();
+ }
+ }
+
+ private static boolean isNonVoidMethod(String name, String desc) {
+ return Type.getReturnType(desc) != Type.VOID_TYPE
+ || name.equals("<init>");
+ }
+
+ public void visitMultiANewArrayInsn(String arg0, int arg1) {
+ mMethodVisitor.visitMultiANewArrayInsn(arg0, arg1);
+ clear();
+ }
+
+ public AnnotationVisitor visitParameterAnnotation(int arg0,
+ String arg1,
+ boolean arg2) {
+ return mMethodVisitor.visitParameterAnnotation(arg0, arg1, arg2);
+ }
+
+ public void visitTableSwitchInsn(int arg0,
+ int arg1,
+ Label arg2,
+ Label[] arg3) {
+ mMethodVisitor.visitTableSwitchInsn(arg0, arg1, arg2, arg3);
+ clear();
+ }
+
+ public void visitTryCatchBlock(Label arg0,
+ Label arg1,
+ Label arg2,
+ String arg3) {
+ mMethodVisitor.visitTryCatchBlock(arg0, arg1, arg2, arg3);
+ clear();
+ }
+
+ public void visitTypeInsn(int opCode, String desc) {
+ mMethodVisitor.visitTypeInsn(opCode, desc);
+ }
+
+ public void visitVarInsn(int opCode, int index) {
+ mMethodVisitor.visitVarInsn(opCode, index);
+ switch (opCode) {
+ case Opcodes.ALOAD:
+ if (index == 0 && !mIsStatic) {
+ pushTextualDescription("this");
+ } else {
+ /*
+ * TODO Translate the index to a local variable name. To be able
+ * to do that we probably have to analyze the class in two steps
+ * since the visit method visitLocalVariable is not called until
+ * after all calls to visitVarInsn.
+ */
+ pushTextualDescription("<localVariable" + index + ">");
+ }
+ break;
+ case Opcodes.ASTORE:
+ pop();
+ break;
+ default:
+ clear();
+ }
+ }
+
+ public AnnotationVisitor visitAnnotation(String arg0, boolean arg1) {
+ return mMethodVisitor.visitAnnotation(arg0, arg1);
+ }
+
+ public AnnotationVisitor visitAnnotationDefault() {
+ return mMethodVisitor.visitAnnotationDefault();
+ }
+
+ public void visitAttribute(Attribute arg0) {
+ mMethodVisitor.visitAttribute(arg0);
+ }
+
+ public void visitMaxs(int arg0, int arg1) {
+ mMethodVisitor.visitMaxs(arg0, arg1);
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.analyzer;
+
+import TransactionalIO.core.TransactionalFile;
+import static com.enea.jcarder.common.contexts.ContextFileReader.CONTEXTS_DB_FILENAME;
+import static com.enea.jcarder.common.contexts.ContextFileReader.EVENT_DB_FILENAME;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import com.enea.jcarder.common.LockingContext;
+import com.enea.jcarder.common.contexts.ContextFileReader;
+import com.enea.jcarder.common.contexts.ContextReaderIfc;
+import com.enea.jcarder.common.events.EventFileReader;
+import com.enea.jcarder.util.BuildInformation;
+import com.enea.jcarder.util.InvalidOptionException;
+import com.enea.jcarder.util.OptionParser;
+import com.enea.jcarder.util.logging.AppendableHandler;
+import com.enea.jcarder.util.logging.Handler;
+import com.enea.jcarder.util.logging.Logger;
+import com.enea.jcarder.util.logging.Logger.Level;
+
+/**
+ * The main class of the JCarder analyzer.
+ */
+public final class Analyzer {
+
+ enum OutputMode { INCLUDE_ALL,
+ INCLUDE_CYCLES,
+ INCLUDE_ONLY_MULTI_THREADED_CYCLES };
+
+ /*
+ * Cycles with only one thread can never cause a deadlock, but it might be
+ * possible that basic tests of a single class are very simplified and use
+ * only a single thread where a real program might invoke the methods from
+ * several different threads. Therefore single-threaded cycles are also
+ * interesting to detect and include by default.
+ */
+ private OutputMode mOutputMode = OutputMode.INCLUDE_CYCLES;
+ private boolean mIncludePackages = false;
+ private boolean mPrintDetails = false;
+ private Logger mLogger;
+ final private Level mLogLevel = Logger.Level.INFO;
+ private String mInputDirectory = ".";
+
+ public static void main(String[] args) {
+ new Analyzer().start(args);
+ }
+
+ public void start(String[] args) {
+ parseArguments(args);
+ initLogger();
+ LockGraphBuilder graphBuilder = new LockGraphBuilder();
+ final ContextReaderIfc contextReader;
+
+ try {
+ contextReader =
+ new ContextFileReader(mLogger, new File(mInputDirectory,
+ CONTEXTS_DB_FILENAME));
+
+ EventFileReader eventReader = new EventFileReader(mLogger);
+ eventReader.parseFile(new File(mInputDirectory, EVENT_DB_FILENAME),
+ graphBuilder);
+ }
+ catch (IOException e) {
+ mLogger.severe("Error while reading result database: "
+ + e.getMessage());
+ return;
+ }
+ printInitiallyLoadedStatistics(graphBuilder.getAllLocks());
+
+ CycleDetector cycleDetector = new CycleDetector(mLogger);
+ cycleDetector.analyzeLockNodes(graphBuilder.getAllLocks());
+ printCycleAnalysisStatistics(cycleDetector);
+
+ if (mOutputMode == OutputMode.INCLUDE_ALL) {
+ printDetailsIfEnabled(cycleDetector.getCycles(), contextReader);
+ try {
+ generatGraphvizFileForAllNodes(graphBuilder, contextReader);
+ } catch (IOException e) {
+ mLogger.severe("Error while generating Graphviz file: "
+ + e.getMessage());
+ }
+ } else {
+ if (mOutputMode == OutputMode.INCLUDE_ONLY_MULTI_THREADED_CYCLES) {
+ cycleDetector.removeSingleThreadedCycles();
+ }
+ if (cycleDetector.getCycles().isEmpty()) {
+ System.out.println("No cycles found!");
+ return;
+ }
+ graphBuilder.clear(); // Help GC.
+ /*
+ * TODO Also clear all references in LockNode.mOutgoingEdges to
+ * avoid keeping references to a lot of LockEdge and LockNode
+ * objects in order to release as much memory as possible for the
+ * memory mapped file?
+ *
+ * It is not necessary to use the DuplicateEdgeshandler since those
+ * duplicates are removed anyway when cycles that are alike are
+ * removed.
+ */
+ cycleDetector.removeAlikeCycles(contextReader);
+
+ printDetailsIfEnabled(cycleDetector.getCycles(), contextReader);
+ try {
+ generateGraphvizFilesForCycles(contextReader, cycleDetector);
+ } catch (IOException e) {
+ mLogger.severe("Error while generating Graphviz file: "
+ + e.getMessage());
+ }
+ }
+ }
+
+ private void initLogger() {
+ Collection<Handler> handlers = new ArrayList<Handler>();
+ // try {
+ handlers.add(new AppendableHandler(System.out,
+ Logger.Level.CONFIG,
+ "{message}\n"));
+ // handlers.add(new AppendableHandler(new PrintStream(new File("analyzer.log")), Logger.Level.CONFIG, "{message}\n", new TransactionalFile("analyzer.log", "rw")));
+ /// } catch (FileNotFoundException ex) {
+ // java.util.logging.Logger.getLogger(Analyzer.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
+ // }
+ mLogger = new Logger(handlers, mLogLevel);
+ }
+
+ private void generateGraphvizFilesForCycles(ContextReaderIfc reader,
+ CycleDetector cycleDetector)
+ throws IOException {
+ System.out.println();
+ int index = 0;
+ Collection<HashSet<LockEdge>> cycles =
+ cycleDetector.mergeCyclesWithIdenticalLocks();
+ for (HashSet<LockEdge> edges : cycles) {
+ if (index >= 100) {
+ System.out.println("Aborting. Too many cycles!");
+ break;
+ }
+ GraphvizGenerator graphvizGenerator = new GraphvizGenerator();
+ createGraphvizFile(graphvizGenerator.generate(edges,
+ reader,
+ mIncludePackages),
+ index++);
+ }
+ }
+
+ private void printCycleAnalysisStatistics(CycleDetector cycleDetector) {
+ System.out.println("\nCycle analysis result: ");
+ System.out.println(" Cycles: "
+ + cycleDetector.getCycles().size());
+ System.out.println(" Edges in cycles: "
+ + cycleDetector.getNumberOfEdges());
+ System.out.println(" Nodes in cycles: "
+ + cycleDetector.getNumberOfNodes());
+ System.out.println(" Max cycle depth: "
+ + cycleDetector.getMaxCycleDepth());
+ System.out.println(" Max graph depth: "
+ + cycleDetector.getMaxDepth());
+ System.out.println();
+ }
+
+ private void generatGraphvizFileForAllNodes(LockGraphBuilder graphBuilder,
+ ContextReaderIfc reader)
+ throws IOException {
+ DuplicatedEdgesHandler.mergeDuplicatedEdges(graphBuilder.getAllLocks(),
+ reader);
+ // TODO Print statistics about removed duplicates?
+ LinkedList<LockEdge> allEdges = new LinkedList<LockEdge>();
+ for (LockNode node : graphBuilder.getAllLocks()) {
+ allEdges.addAll(node.getOutgoingEdges());
+ }
+ GraphvizGenerator graphvizGenerator = new GraphvizGenerator();
+ createGraphvizFile(graphvizGenerator.generate(allEdges,
+ reader,
+ mIncludePackages),
+ 0);
+ }
+
+ private void parseArguments(String[] args) {
+ OptionParser op = new OptionParser();
+ configureOptionParser(op);
+
+ try {
+ op.parse(args);
+ } catch (InvalidOptionException e) {
+ handleBadOption(op, e.getMessage());
+ }
+
+ handleOptions(op);
+ }
+
+ private void configureOptionParser(OptionParser op) {
+ /*
+ * TODO Add parameters for filtering (including & excluding) specific
+ * locks and edges for example by specifying thread names, object
+ * classes, method names or packages?
+ */
+
+ op.addOption("-help",
+ "Print this help text");
+ op.addOption("-d <directory>",
+ "Read results to analyze from <directory> (default:"
+ + " current directory)");
+ op.addOption("-includepackages",
+ "Include packages (not only class names) in graph");
+ op.addOption("-outputmode <mode>",
+ "Set output mode to <mode> (one of ALL, CYCLES, MTCYCLES);"
+ + " ALL: include everything;"
+ + " CYCLES: only include cycles (this is the default);"
+ + " MTCYCLES: only include multi-thread cycles");
+ op.addOption("-printdetails",
+ "Print details");
+ op.addOption("-version",
+ "Print program version");
+ }
+
+ private void handleOptions(OptionParser op) {
+ Map<String, String> options = op.getOptions();
+ for (String option : options.keySet()) {
+ if (option.equals("-help")) {
+ printHelpText(System.out, op);
+ System.exit(0);
+ } else if (option.equals("-i")) {
+ mInputDirectory = options.get(option);
+ } else if (option.equals("-includepackages")) {
+ mIncludePackages = true;
+ } else if (option.equals("-outputmode")) {
+ String value = options.get(option);
+ if (value.equalsIgnoreCase("all")) {
+ mOutputMode = OutputMode.INCLUDE_ALL;
+ } else if (value.equalsIgnoreCase("cycles")) {
+ mOutputMode = OutputMode.INCLUDE_CYCLES;
+ } else if (value.equalsIgnoreCase("mtcycles")) {
+ mOutputMode = OutputMode.INCLUDE_ONLY_MULTI_THREADED_CYCLES;
+ } else {
+ handleBadOption(op, "bad output mode");
+ }
+ } else if (option.equals("-printdetails")) {
+ mPrintDetails = true;
+ } else if (option.equals("-version")) {
+ BuildInformation.printLongBuildInformation();
+ System.exit(0);
+ }
+ }
+ }
+
+ private void printHelpText(PrintStream stream, OptionParser op) {
+ stream.print("Usage: java -jar jcarder.jar [options]\n\n");
+ stream.print("Options:\n");
+ stream.print(op.getOptionHelp());
+ }
+
+ private void handleBadOption(OptionParser optionParser, String message) {
+ System.err.println("JCarder: " + message);
+ printHelpText(System.err, optionParser);
+ System.exit(1);
+ }
+
+ private void printDetailsIfEnabled(Iterable<Cycle> cycles,
+ ContextReaderIfc reader) {
+ if (!mPrintDetails) {
+ return;
+ }
+ SortedSet<String> threads = new TreeSet<String>();
+ SortedSet<String> methods = new TreeSet<String>();
+ for (Cycle cycle : cycles) {
+ for (LockEdge edge : cycle.getEdges()) {
+ LockingContext source =
+ reader.readContext(edge.getSourceLockingContextId());
+ LockingContext target =
+ reader.readContext(edge.getTargetLockingContextId());
+ threads.add(source.getThreadName());
+ threads.add(target.getThreadName());
+ methods.add(source.getMethodWithClass());
+ methods.add(target.getMethodWithClass());
+ }
+ }
+ System.out.println();
+ System.out.println("Threads involved in cycles:");
+ for (String thread : threads) {
+ System.out.println(" " + thread);
+ }
+ System.out.println();
+ System.out.println("Methods involved in cycles:");
+ for (String method : methods) {
+ System.out.println(" " + method);
+ }
+ System.out.println();
+ }
+
+
+ private void printInitiallyLoadedStatistics(Iterable<LockNode> locks) {
+ int numberOfNodes = 0;
+ int numberOfUniqueEdges = 0;
+ int numberOfDuplicatedEdges = 0;
+ for (LockNode lock : locks) {
+ numberOfNodes++;
+ numberOfUniqueEdges += lock.numberOfUniqueEdges();
+ numberOfDuplicatedEdges += lock.numberOfDuplicatedEdges();
+ }
+ System.out.println("\nLoaded from database files:");
+ System.out.println(" Nodes: " + numberOfNodes);
+ System.out.println(" Edges: " + numberOfUniqueEdges
+ + " (excluding " + numberOfDuplicatedEdges
+ + " duplicated)");
+ }
+
+ private void createGraphvizFile(String s, int index) throws IOException {
+ File file = new File("jcarder_result_" + index + ".dot");
+ System.out.println("Writing Graphviz file: " + file.getAbsolutePath());
+ FileWriter fw = new FileWriter(file);
+ fw.write(s);
+ fw.flush();
+ fw.close();
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.analyzer;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+import com.enea.jcarder.common.contexts.ContextReaderIfc;
+
+/**
+ * An instance of this class represents a single cycle of edges. The edges must
+ * form a single circle witout any alternative paths. If there are alternative
+ * paths in a graph cycle, those cycles will be split into separate Cycle
+ * objects.
+ *
+ * TODO Write basic tests.
+ */
+class Cycle {
+ final HashSet<LockEdge> mEdgesInCycle = new HashSet<LockEdge>();
+
+ Cycle(Collection<LockEdge> edgesInTheCycle) {
+ mEdgesInCycle.addAll(edgesInTheCycle);
+ assert mEdgesInCycle.size() >= 2;
+ }
+
+ HashSet<LockEdge> getEdges() {
+ return mEdgesInCycle;
+ }
+
+ HashSet<LockNode> getNodes() {
+ HashSet<LockNode> nodes = new HashSet<LockNode>();
+ for (LockEdge edge : mEdgesInCycle) {
+ /*
+ * All sources will be included if we get all the targets, since it
+ * is a cycle.
+ */
+ nodes.add(edge.getTarget());
+ }
+ return nodes;
+ }
+
+ void updateNodeCycleStatus() {
+ final LockNode.CycleType type;
+ if (isSingleThreaded()) {
+ type = LockNode.CycleType.SINGLE_THREADED_CYCLE;
+ } else {
+ type = LockNode.CycleType.CYCLE;
+ }
+ for (LockEdge edge : mEdgesInCycle) {
+ edge.getSource().raiseCycleType(type);
+ edge.getTarget().raiseCycleType(type);
+ }
+ }
+
+ boolean isSingleThreaded() {
+ // TODO Cache the result to improve performance?
+ final Iterator<LockEdge> iter = mEdgesInCycle.iterator();
+ if (iter.hasNext()) {
+ final long firstThreadId = iter.next().getThreadId();
+ while (iter.hasNext()) {
+ if (firstThreadId != iter.next().getThreadId()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ boolean alike(Cycle other, ContextReaderIfc reader) {
+ if (this.equals(other)) {
+ return true;
+ }
+ if (mEdgesInCycle.size() != other.mEdgesInCycle.size()) {
+ return false;
+ }
+ if (isSingleThreaded() != other.isSingleThreaded()) {
+ return false;
+ }
+ LinkedList<LockEdge> otherEdges =
+ new LinkedList<LockEdge>(other.mEdgesInCycle);
+ // TODO Refactor the following code?
+ outerLoop:
+ for (LockEdge edge : mEdgesInCycle) {
+ Iterator<LockEdge> iter = otherEdges.iterator();
+ while (iter.hasNext()) {
+ if (edge.alike(iter.next(), reader)) {
+ iter.remove();
+ continue outerLoop;
+ }
+ }
+ return false;
+ }
+ return true;
+ }
+
+ public boolean equals(Object obj) {
+ try {
+ Cycle other = (Cycle) obj;
+ return mEdgesInCycle.equals(other.mEdgesInCycle);
+ } catch (ClassCastException e) {
+ return false;
+ }
+ }
+
+ public int hashCode() {
+ return mEdgesInCycle.hashCode();
+ }
+
+
+ public String toString() {
+ return mEdgesInCycle.toString();
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.analyzer;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+
+import net.jcip.annotations.NotThreadSafe;
+
+import com.enea.jcarder.common.contexts.ContextReaderIfc;
+import com.enea.jcarder.util.Counter;
+import com.enea.jcarder.util.MaxValueCounter;
+import com.enea.jcarder.util.logging.Logger;
+
+/**
+ * This class is responsible for finding and managing cycles.
+ *
+ * TODO Add possibility to ignore cycles guarded by a common lock.
+ *
+ * TODO Add possibility to ignore cycles created by two threads that cannot
+ * possibly run at the same time. Is it possible to achieve that by tracking
+ * Thread.start() and Thread.join()?
+ *
+ * TODO Add more basic tests for this class.
+ */
+@NotThreadSafe
+class CycleDetector {
+ private final HashSet<Cycle> mCycles;
+ private final Logger mLogger;
+ private final MaxValueCounter mMaxDepth;
+ private final MaxValueCounter mMaxCycleDepth;
+ private final MaxValueCounter mNoOfCycles;
+ private final Counter mNoOfCreatedCycleObjects;
+
+ CycleDetector(Logger logger) {
+ mLogger = logger;
+ mCycles = new HashSet<Cycle>();
+ mMaxDepth = new MaxValueCounter("Graph Depth", mLogger);
+ mMaxCycleDepth = new MaxValueCounter("Cycle Depth", mLogger);
+ mNoOfCycles = new MaxValueCounter("Found cycles", mLogger);
+ mNoOfCreatedCycleObjects = new Counter("Created cycle objects",
+ mLogger,
+ 100000);
+ }
+
+ /**
+ * Analyze a set of LockNodes and LockEdges. All cycles they form will be
+ * stored within this class.
+ */
+ void analyzeLockNodes(final Iterable<LockNode> nodes) {
+ ArrayList<LockNode> nodesOnStack = new ArrayList<LockNode>(10);
+ ArrayList<LockEdge> edgesOnStack = new ArrayList<LockEdge>(10);
+ HashSet<LockNode> visitedNodes = new HashSet<LockNode>();
+ HashSet<LockEdge> visitedEdges = new HashSet<LockEdge>();
+ for (LockNode lock : nodes) {
+ if (!visitedNodes.contains(lock)) {
+ analyzeNode(lock, nodesOnStack, edgesOnStack, visitedNodes,
+ visitedEdges);
+ }
+ }
+ for (Cycle cycle : mCycles) {
+ cycle.updateNodeCycleStatus();
+ }
+ }
+
+ MaxValueCounter getMaxDepth() {
+ return mMaxDepth;
+ }
+
+ MaxValueCounter getMaxCycleDepth() {
+ return mMaxCycleDepth;
+ }
+
+ private void analyzeNode(final LockNode node,
+ final ArrayList<LockNode> nodesOnStack,
+ final ArrayList<LockEdge> edgesOnStack,
+ final HashSet<LockNode> visitedNodes,
+ final HashSet<LockEdge> visitedEdges) {
+ visitedNodes.add(node);
+ nodesOnStack.add(node);
+ for (LockEdge edge : node.getOutgoingEdges()) {
+ edgesOnStack.add(edge);
+ analyzeEdge(edge, nodesOnStack, edgesOnStack, visitedNodes,
+ visitedEdges);
+ edgesOnStack.remove(edgesOnStack.size() - 1);
+ }
+ nodesOnStack.remove(nodesOnStack.size() - 1);
+ }
+
+ private void analyzeEdge(final LockEdge edge,
+ final ArrayList<LockNode> nodesOnStack,
+ final ArrayList<LockEdge> edgesOnStack,
+ final HashSet<LockNode> visitedNodes,
+ final HashSet<LockEdge> visitedEdges) {
+ if (!visitedEdges.contains(edge)) {
+ mMaxDepth.set(nodesOnStack.size());
+ final int index = nodesOnStack.indexOf(edge.getTarget());
+ if (index >= 0) {
+ final List<LockEdge> edgesInCycle =
+ edgesOnStack.subList(index, edgesOnStack.size());
+ mNoOfCreatedCycleObjects.increment();
+ mMaxCycleDepth.set(edgesInCycle.size());
+ mCycles.add(new Cycle(edgesInCycle));
+ mNoOfCycles.set(mCycles.size());
+ /*
+ * Keeping the first edge from the cycle in the visitedEdges
+ * list is an optimization that avoids unnecessary (as I
+ * believe) repeated checks. The other edges have to be removed,
+ * otherwise all cycles won't be found. See the testcases for
+ * examples of such cases.
+ */
+ List<LockEdge> edgesToRemove =
+ edgesInCycle.subList(1, edgesInCycle.size());
+ visitedEdges.removeAll(edgesToRemove);
+ } else {
+ visitedEdges.add(edge);
+ analyzeNode(edge.getTarget(),
+ nodesOnStack,
+ edgesOnStack,
+ visitedNodes,
+ visitedEdges);
+ }
+ }
+ }
+
+
+ HashSet<Cycle> getCycles() {
+ return mCycles;
+ }
+
+ private static boolean containsAlike(Cycle cycle, Iterable<Cycle> others,
+ ContextReaderIfc reader) {
+ for (Cycle other : others) {
+ if (cycle.alike(other, reader)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Reducing the number of cycles by ignoring those that are duplicates (very
+ * similar) to another cycle.
+ */
+ void removeAlikeCycles(ContextReaderIfc reader) {
+ int removedCycles = 0;
+ ArrayList<Cycle> uniqueCycles = new ArrayList<Cycle>();
+ Iterator<Cycle> iter = mCycles.iterator();
+ while (iter.hasNext()) {
+ final Cycle cycle = iter.next();
+ if (containsAlike(cycle, uniqueCycles, reader)) {
+ iter.remove();
+ removedCycles++;
+ } else {
+ uniqueCycles.add(cycle);
+ }
+ }
+ mLogger.info("Ignoring " + removedCycles
+ + " almost identical cycle(s).");
+ assert uniqueCycles.equals(new ArrayList<Cycle>(mCycles));
+ }
+
+ /**
+ * Remove cycles that are formed by a only one thread.
+ */
+ void removeSingleThreadedCycles() {
+ int removedCycles = 0;
+ Iterator<Cycle> iter = mCycles.iterator();
+ while (iter.hasNext()) {
+ final Cycle cycle = iter.next();
+ if (cycle.isSingleThreaded()) {
+ iter.remove();
+ removedCycles++;
+ }
+ }
+ mLogger.info("Ignoring "
+ + removedCycles
+ + " single threaded cycle(s).");
+ }
+
+ /**
+ * Get the total number of edges in all known cycles.
+ */
+ int getNumberOfEdges() {
+ HashSet<LockEdge> edges = new HashSet<LockEdge>();
+ for (Cycle cycle : mCycles) {
+ edges.addAll(cycle.getEdges());
+ }
+ return edges.size();
+ }
+
+ /**
+ * Get the total number of nodes in all known cycles.
+ */
+ int getNumberOfNodes() {
+ HashSet<LockNode> nodes = new HashSet<LockNode>();
+ for (Cycle cycle : mCycles) {
+ for (LockEdge edge : cycle.getEdges()) {
+ nodes.add(edge.getSource());
+ nodes.add(edge.getTarget());
+ }
+ }
+ return nodes.size();
+ }
+
+ /**
+ * Find out the cycles that consist of identical locks and group them
+ * together. Then return all edges in each group.
+ *
+ * The data structure in the CycleDetector class is unaffected.
+ */
+ Collection<HashSet<LockEdge>> mergeCyclesWithIdenticalLocks() {
+ /*
+ * TODO Refactor this method? The temporary data structure is too
+ * complex?
+ */
+ HashMap<HashSet<LockNode>, HashSet<LockEdge>> setOfNodesToEdgesMap =
+ new HashMap<HashSet<LockNode>, HashSet<LockEdge>>();
+ for (Cycle cycle : mCycles) {
+ final HashSet<LockNode> nodes = cycle.getNodes();
+ HashSet<LockEdge> edges = setOfNodesToEdgesMap.get(nodes);
+ if (edges == null) {
+ edges = new HashSet<LockEdge>();
+ setOfNodesToEdgesMap.put(nodes, edges);
+ }
+ edges.addAll(cycle.getEdges());
+ }
+ return setOfNodesToEdgesMap.values();
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.analyzer;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeSet;
+
+import com.enea.jcarder.common.LockingContext;
+import com.enea.jcarder.common.contexts.ContextReaderIfc;
+
+/**
+ * This class contains functionality for merging edges that have the same source
+ * and target nodes and identical thread IDs and locking contexts content, but
+ * different locking context IDs.
+ *
+ * Such a merge might be desirable since the producer of the context file is not
+ * required to guarantee that identical locking contexts always get the same
+ * IDs.
+ *
+ * @TODO Add basic tests for this class.
+ */
+public final class DuplicatedEdgesHandler {
+ private final Iterable<LockNode> mLockNodes;
+ private final Map<Integer, Integer> mContextIdTranslation;
+ private final Map<LockingContext, TreeSet<Integer>> mContextToIdMap;
+
+ /**
+ * The constructor is made private to prevent that someone creates an
+ * instance of this class and then forgets to release the reference to it.
+ * That would be undesirable since the mContextToIdMap structure in this
+ * class might be very large and should be garbage collected as soon as
+ * possible.
+ *
+ * @see DuplicatedEdgesHandler.mergeDuplicatedEdges() instead.
+ */
+ private DuplicatedEdgesHandler(Iterable<LockNode> lockNodes,
+ ContextReaderIfc reader) {
+ mLockNodes = lockNodes;
+ mContextIdTranslation = populateTranslationMap();
+ mContextToIdMap = createContextToIdMap(reader);
+ }
+
+ public static void mergeDuplicatedEdges(Iterable<LockNode> lockNodes,
+ ContextReaderIfc reader) {
+ DuplicatedEdgesHandler handler = new DuplicatedEdgesHandler(lockNodes,
+ reader);
+ handler.updateContextIdTranslationMap();
+ handler.updateEdgesWithTranslationMap();
+ }
+
+ private void updateEdgesWithTranslationMap() {
+ for (LockNode node : mLockNodes) {
+ node.translateContextIds(mContextIdTranslation);
+ }
+ }
+
+ private Map<Integer, Integer> populateTranslationMap() {
+ final HashMap<Integer, Integer> contextIds =
+ new HashMap<Integer, Integer>();
+ for (LockNode node : mLockNodes) {
+ node.populateContextIdTranslationMap(contextIds);
+ }
+ return contextIds;
+ }
+
+ private Map<LockingContext, TreeSet<Integer>>
+ createContextToIdMap(ContextReaderIfc reader) {
+ final Map<LockingContext, TreeSet<Integer>> contextToId =
+ new HashMap<LockingContext, TreeSet<Integer>>();
+ for (Integer id : mContextIdTranslation.values()) {
+ LockingContext context = reader.readContext(id);
+ TreeSet<Integer> ids = contextToId.get(context);
+ if (ids == null) {
+ ids = new TreeSet<Integer>();
+ contextToId.put(context, ids);
+ }
+ ids.add(id);
+ }
+ return contextToId;
+ }
+
+ private void updateContextIdTranslationMap() {
+ for (TreeSet<Integer> ids : mContextToIdMap.values()) {
+ if (ids.size() > 1) {
+ Iterator<Integer> iter = ids.iterator();
+ Integer firstId = iter.next();
+ while (iter.hasNext()) {
+ mContextIdTranslation.put(iter.next(), firstId);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.analyzer;
+
+import java.util.HashSet;
+
+import com.enea.jcarder.common.LockingContext;
+import com.enea.jcarder.common.contexts.ContextReaderIfc;
+
+/**
+ * This class can be used to generate a Graphviz <http://www.graphviz.org> graph
+ * as a string.
+ *
+ * TODO Add tooltips to the graph? The tooltips might for example contain the
+ * package names of classes.
+ *
+ * TODO If there are to many edges, merge them together in the graph and add an
+ * href link to an html page that describes them all?
+ *
+ * TODO Optionaly merge edges that are identical except for the threads?
+ */
+final class GraphvizGenerator {
+ private static final String EDGE_LABEL_FORMAT =
+ " [fontsize=10, label=<\n" +
+ " <table align=\"left\" border=\"0\" cellborder=\"0\"\n" +
+ " cellspacing=\"0\" cellpadding=\"0\">\n" +
+ " <tr>\n" +
+ " <td align=\"left\"> </td>\n" +
+ " <td align=\"left\" colspan=\"2\">" +
+ "Thread: %1$s<br align=\"left\"/>" +
+ "</td>\n" +
+ " </tr>\n" +
+ " <tr>\n" +
+ " <td align=\"left\"> </td>\n" +
+ " <td align=\"left\" colspan=\"2\">" +
+ "holding: %2$s<br align=\"left\"/>" +
+ "</td>\n" +
+ " </tr>\n" +
+ " <tr>\n" +
+ " <td align=\"left\"> </td>\n" +
+ " <td align=\"left\"> </td>\n" +
+ " <td align=\"left\">" +
+ "in: %3$s<br align=\"left\"/>" +
+ "</td>\n" +
+ " </tr>\n" +
+ " <tr>\n" +
+ " <td align=\"left\"> </td>\n" +
+ " <td align=\"left\" colspan=\"2\">" +
+ "taking: %4$s<br align=\"left\"/>" +
+ "</td>\n" +
+ " </tr> \n" +
+ " <tr>\n" +
+ " <td align=\"left\"> </td>\n" +
+ " <td align=\"left\"> </td>\n" +
+ " <td align=\"left\">" +
+ "in: %5$s<br align=\"left\"/>" +
+ "</td>\n" +
+ " </tr>\n" +
+ " </table>\n" +
+ " >]";
+
+ public String generate(Iterable<LockEdge> edgesToBePrinted,
+ ContextReaderIfc reader,
+ boolean includePackages) {
+ StringBuffer sb = new StringBuffer();
+ sb.append("digraph G {\n");
+ sb.append(" node [shape=ellipse, style=filled, fontsize=12];\n");
+ final HashSet<LockNode> alreadyAppendedNodes = new HashSet<LockNode>();
+ for (LockEdge edge : edgesToBePrinted) {
+ appendNodeIfNotAppended(reader,
+ sb,
+ alreadyAppendedNodes,
+ edge.getSource());
+ appendNodeIfNotAppended(reader,
+ sb,
+ alreadyAppendedNodes,
+ edge.getTarget());
+ sb.append(" " + edge.getSource().toString() + "");
+ sb.append(" -> " + edge.getTarget().toString() + "");
+ sb.append(createEdgeLabel(reader, edge, includePackages));
+ sb.append(";\n");
+ }
+ sb.append("}\n");
+ return sb.toString();
+ }
+
+ private String createEdgeLabel(ContextReaderIfc reader,
+ LockEdge edge,
+ boolean includePackages) {
+ final LockingContext source =
+ reader.readContext(edge.getSourceLockingContextId());
+ final LockingContext target =
+ reader.readContext(edge.getTargetLockingContextId());
+ return String.format(EDGE_LABEL_FORMAT,
+ escape(handlePackage(target.getThreadName(),
+ includePackages)),
+ escape(handlePackage(source.getLockReference(),
+ includePackages)),
+ escape(handlePackage(source.getMethodWithClass(),
+ includePackages)),
+ escape(handlePackage(target.getLockReference(),
+ includePackages)),
+ escape(handlePackage(target.getMethodWithClass(),
+ includePackages)));
+ }
+
+ private String handlePackage(String s,
+ boolean includePackages) {
+ String[] parts = s.split("\\.");
+ if (parts.length >= 2 && !includePackages) {
+ return parts[parts.length - 2] + "." + parts[parts.length - 1];
+ } else {
+ return s;
+ }
+ }
+
+ private static String escape(String s) {
+ return s.replace("&", "&")
+ .replace("<", "<")
+ .replace(">", ">")
+ .replace("\"", """)
+ .replace("\'", "'");
+ }
+
+ private String getLockNodeString(LockNode node,
+ ContextReaderIfc reader) {
+ final String color;
+ switch (node.getCycleType()) {
+ case CYCLE:
+ color = "firebrick1";
+ break;
+ case SINGLE_THREADED_CYCLE:
+ color = "yellow";
+ break;
+ default:
+ color = "white";
+ }
+ return " " + node.toString() + " [label = \""
+ + escape(reader.readLock(node.getLockId()).toString())
+ + "\" , fillcolor=" + color + "];\n";
+ }
+
+ private void appendNodeIfNotAppended(ContextReaderIfc reader,
+ StringBuffer sb,
+ HashSet<LockNode> alreadyAppendedNodes,
+ LockNode node) {
+ if (!alreadyAppendedNodes.contains(node)) {
+ alreadyAppendedNodes.add(node);
+ sb.append(getLockNodeString(node, reader));
+ }
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.analyzer;
+
+import java.util.Map;
+
+import net.jcip.annotations.NotThreadSafe;
+
+import com.enea.jcarder.common.LockingContext;
+import com.enea.jcarder.common.contexts.ContextReaderIfc;
+
+/**
+ * A LockEdge instance represents a directed edge from a source LockNode to a
+ * target LockNode.
+ */
+@NotThreadSafe
+class LockEdge {
+ private final LockNode mSource;
+ private final LockNode mTarget;
+ private final long mThreadId; // The thread that did the synchronization.
+ private int mSourceContextId;
+ private int mTargetContextId;
+ private long mNumberOfDuplicates;
+
+ LockEdge(LockNode source,
+ LockNode target,
+ long threadId,
+ int sourceLockingContextId,
+ int targetLockingContextId) {
+ mSource = source;
+ mTarget = target;
+ mThreadId = threadId;
+ mSourceContextId = sourceLockingContextId;
+ mTargetContextId = targetLockingContextId;
+ mNumberOfDuplicates = 0;
+ }
+
+ void merge(LockEdge other) {
+ assert this.equals(other);
+ mNumberOfDuplicates += (other.mNumberOfDuplicates + 1);
+ }
+
+ long getDuplicates() {
+ return mNumberOfDuplicates;
+ }
+
+ boolean alike(LockEdge other, ContextReaderIfc reader) {
+ /*
+ * TODO Some kind of cache to improve performance? Note that the context
+ * IDs are not declared final.
+ */
+ LockingContext thisSourceContext =
+ reader.readContext(mSourceContextId);
+ LockingContext otherSourceContext =
+ reader.readContext(other.mSourceContextId);
+ LockingContext thisTargetContext =
+ reader.readContext(mTargetContextId);
+ LockingContext otherTargetContext =
+ reader.readContext(other.mTargetContextId);
+ return thisSourceContext.alike(otherSourceContext)
+ && thisTargetContext.alike(otherTargetContext)
+ && mSource.alike(other.mSource, reader)
+ && mTarget.alike(other.mTarget, reader);
+ }
+
+ public boolean equals(Object obj) {
+ /*
+ * TODO It might be a potential problem to use LockEdges in HashMaps
+ * since they are mutable and this equals method depends on them?
+ */
+ try {
+ LockEdge other = (LockEdge) obj;
+ return (mTarget.getLockId() == other.mTarget.getLockId())
+ && (mSource.getLockId() == other.mSource.getLockId())
+ && (mThreadId == other.mThreadId)
+ && (mSourceContextId == other.mSourceContextId)
+ && (mTargetContextId == other.mTargetContextId);
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ public int hashCode() {
+ // TODO Improve hashCode algorithm to improve performance?
+ return mTarget.getLockId() + mSource.getLockId();
+ }
+
+ LockNode getTarget() {
+ return mTarget;
+ }
+
+ LockNode getSource() {
+ return mSource;
+ }
+
+ int getSourceLockingContextId() {
+ return mSourceContextId;
+ }
+
+ int getTargetLockingContextId() {
+ return mTargetContextId;
+ }
+
+ /**
+ * Translate the source and target context ID according to a translation
+ * map.
+ */
+ void translateContextIds(Map<Integer, Integer> translation) {
+ final Integer newSourceId = translation.get(mSourceContextId);
+ if (newSourceId != null && newSourceId != mSourceContextId) {
+ mSourceContextId = newSourceId;
+ }
+ final Integer newTargetId = translation.get(mTargetContextId);
+ if (newTargetId != null && newSourceId != mTargetContextId) {
+ mTargetContextId = newTargetId;
+ }
+ }
+
+ long getThreadId() {
+ return mThreadId;
+ }
+
+ public String toString() {
+ return " " + mSource + "->" + mTarget;
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.analyzer;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import java.util.Vector;
+import net.jcip.annotations.NotThreadSafe;
+
+import com.enea.jcarder.common.events.LockEventListenerIfc;
+
+/**
+ * This class is responsible for constructing a structure of LockNode and
+ * LockEdge objects from incoming lock events.
+ *
+ * TODO Add basic tests for this class.
+ */
+@NotThreadSafe
+class LockGraphBuilder implements LockEventListenerIfc {
+ private HashMap<Integer, LockNode> mLocks =
+ new HashMap<Integer, LockNode>();
+
+ LockNode getLockNode(int lockId) {
+ LockNode lockNode = mLocks.get(lockId);
+ if (lockNode == null) {
+ lockNode = new LockNode(lockId);
+ mLocks.put(lockId, lockNode);
+ }
+ return lockNode;
+ }
+
+ public void onLockEvent(int lockId,
+ int lockingContextId,
+ int lastTakenLockId,
+ int lastTakenLockingContectId,
+ long threadId) {
+ if (lastTakenLockId >= 0) {
+ final LockNode sourceLock = getLockNode(lastTakenLockId);
+ final LockNode targetLock = getLockNode(lockId);
+ final LockEdge edge = new LockEdge(sourceLock,
+ targetLock,
+ threadId,
+ lastTakenLockingContectId,
+ lockingContextId);
+ sourceLock.addOutgoingEdge(edge);
+ }
+ }
+
+ void clear() {
+ mLocks.clear();
+ }
+
+ Iterable<LockNode> getAllLocks() {
+ return mLocks.values();
+ }
+
+ public void tronLockEvent(Vector srgs) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.analyzer;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import net.jcip.annotations.NotThreadSafe;
+
+import com.enea.jcarder.common.contexts.ContextReaderIfc;
+
+/**
+ * A LockNode instance represents a lock in a graph.
+ */
+@NotThreadSafe
+class LockNode {
+ enum CycleType { NO_CYCLE, SINGLE_THREADED_CYCLE, CYCLE };
+
+ private final int mLockId;
+ private Map<LockEdge, LockEdge> mOutgoingEdges;
+ private CycleType mCycleType = CycleType.NO_CYCLE;
+
+ LockNode(final int lockId) {
+ mLockId = lockId;
+ mOutgoingEdges = new HashMap<LockEdge, LockEdge>();
+ }
+
+ int getLockId() {
+ return mLockId;
+ }
+
+ CycleType getCycleType() {
+ return mCycleType;
+ }
+
+ void raiseCycleType(CycleType newCycleType) {
+ if (newCycleType.compareTo(mCycleType) > 0) {
+ mCycleType = newCycleType;
+ }
+ }
+
+ void addOutgoingEdge(LockEdge newEdge) {
+ LockEdge existingEdge = mOutgoingEdges.get(newEdge);
+ if (existingEdge == null) {
+ mOutgoingEdges.put(newEdge, newEdge);
+ } else {
+ existingEdge.merge(newEdge);
+ }
+ }
+
+ void populateContextIdTranslationMap(Map<Integer, Integer> translationMap) {
+ for (LockEdge edge : mOutgoingEdges.values()) {
+ translationMap.put(edge.getSourceLockingContextId(),
+ edge.getSourceLockingContextId());
+ translationMap.put(edge.getTargetLockingContextId(),
+ edge.getTargetLockingContextId());
+ }
+ }
+
+ void translateContextIds(Map<Integer, Integer> translation) {
+ Map<LockEdge, LockEdge> oldEdges = mOutgoingEdges;
+ mOutgoingEdges = new HashMap<LockEdge, LockEdge>(oldEdges.size());
+ for (LockEdge edge : oldEdges.values()) {
+ edge.translateContextIds(translation);
+ addOutgoingEdge(edge);
+ }
+ }
+
+ Set<LockEdge> getOutgoingEdges() {
+ return mOutgoingEdges.keySet();
+ }
+
+ public String toString() {
+ return "L_" + mLockId;
+ }
+
+ long numberOfUniqueEdges() {
+ return mOutgoingEdges.size();
+ }
+
+ long numberOfDuplicatedEdges() {
+ long numberOfDuplicatedEdges = 0;
+ for (LockEdge edge : mOutgoingEdges.values()) {
+ numberOfDuplicatedEdges += edge.getDuplicates();
+ }
+ return numberOfDuplicatedEdges;
+ }
+
+ boolean alike(LockNode other, ContextReaderIfc reader) {
+ // TODO Maybe introduce some kind of cache to improve performance?
+ String thisClassName = reader.readLock(mLockId).getClassName();
+ String otherClassName = reader.readLock(other.mLockId).getClassName();
+ return thisClassName.equals(otherClassName);
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.common;
+
+//import net.jcip.annotations.ThreadSafe;
+
+/**
+ * A Lock instance represents a Java monitor object.
+ */
+//@ThreadSafe
+public final class Lock {
+ private final String mClassName;
+ private final int mObjectId;
+
+ public Lock(Object lock) {
+ mClassName = lock.getClass().getName();
+ mObjectId = System.identityHashCode(lock);
+ }
+
+ public Lock(String className, int objectId) {
+ mClassName = className;
+ mObjectId = objectId;
+ }
+
+ public String toString() {
+ return mClassName + '@' + Integer.toHexString(mObjectId).toUpperCase();
+ }
+
+ public int getObjectId() {
+ return mObjectId;
+ }
+
+ public String getClassName() {
+ return mClassName;
+ }
+
+ public int hashCode() {
+ return mObjectId;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Lock other = (Lock) obj;
+ return mObjectId == other.mObjectId
+ && mClassName.equals(other.mClassName);
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.common;
+
+//import net.jcip.annotations.ThreadSafe;
+
+/**
+ * An instance of this class represents the context for the acquiring of a lock.
+ */
+//@ThreadSafe
+public final class LockingContext {
+ /**
+ * The name of the thread that acquired the lock.
+ */
+ private final String mThreadName;
+
+ /**
+ * A textual description of how the lock object was addressed. For example:
+ * "this", "com.enea.jcarder.Foo.mBar" or "com.enea.jcarder.Foo.getLock()"
+ */
+ private final String mLockReference;
+
+ /**
+ * The method that acquired a lock, on the
+ * format "com.enea.jcarder.Foo.bar()".
+ */
+ private final String mMethodWithClass;
+ // TODO Include row number in MethodWithClass?
+
+ public LockingContext(String threadName,
+ String lockReference,
+ String methodWithClass) {
+ mThreadName = threadName;
+ mLockReference = lockReference;
+ mMethodWithClass = methodWithClass;
+ }
+
+ public LockingContext(Thread thread,
+ String lockReference,
+ String methodWithClass) {
+ this(thread.getName(), lockReference, methodWithClass);
+ }
+
+ public String getLockReference() {
+ return mLockReference;
+ }
+
+ public String getMethodWithClass() {
+ return mMethodWithClass;
+ }
+
+ public String getThreadName() {
+ return mThreadName;
+ }
+
+ public boolean alike(LockingContext other) {
+ return mLockReference.equals(other.mLockReference)
+ && mMethodWithClass.equals(other.mMethodWithClass);
+ }
+
+ public boolean equals(Object other) {
+ try {
+ if (other == null) {
+ return false;
+ }
+ // TODO Maybe use interned strings to improve performance?
+ final LockingContext otherContext = (LockingContext) other;
+ return mThreadName.equals(otherContext.mThreadName)
+ && mLockReference.equals(otherContext.mLockReference)
+ && mMethodWithClass.equals(otherContext.mMethodWithClass);
+ } catch (ClassCastException e) {
+ return false;
+ }
+ }
+
+ public int hashCode() {
+ return mThreadName.hashCode()
+ + mMethodWithClass.hashCode()
+ + mLockReference.hashCode();
+ }
+
+ public String toString() {
+ return "Thread: " + mThreadName
+ + " LockRef: " + mLockReference
+ + " Method: " + mMethodWithClass;
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.common.contexts;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.charset.Charset;
+
+//import net.jcip.annotations.NotThreadSafe;
+
+import com.enea.jcarder.common.Lock;
+import com.enea.jcarder.common.LockingContext;
+import com.enea.jcarder.util.logging.Logger;
+
+//@NotThreadSafe
+public final class ContextFileReader
+implements ContextReaderIfc {
+ // TODO Make the directory of the database files configurable?
+ public static final String EVENT_DB_FILENAME = "jcarder_events.db";
+ public static final String CONTEXTS_DB_FILENAME = "jcarder_contexts.db";
+ static final long MAGIC_COOKIE = 3927194112434171438L;
+ static final int MAJOR_VERSION = 1;
+ static final int MINOR_VERSION = 0;
+ static final Charset CHARSET = Charset.forName("UTF-8");
+ private final Logger mLogger;
+ private final ByteBuffer mBuffer;
+
+ public ContextFileReader(Logger logger, File file) throws IOException {
+ mLogger = logger;
+ RandomAccessFile raFile = new RandomAccessFile(file, "r");
+ final String path = file.getCanonicalPath();
+ mLogger.info("Opening for reading: " + path);
+ FileChannel roChannel = raFile.getChannel();
+ if (roChannel.size() > Integer.MAX_VALUE) {
+ throw new IOException("File too large: " + path);
+ }
+ mBuffer = roChannel.map(FileChannel.MapMode.READ_ONLY,
+ 0,
+ (int) roChannel.size());
+ roChannel.close();
+ raFile.close();
+ validateHeader(path);
+ }
+
+ private void validateHeader(String filename) throws IOException {
+ mBuffer.rewind();
+ if (MAGIC_COOKIE != mBuffer.getLong()) {
+ throw new IOException("Invalid file contents in: " + filename);
+ }
+ final int majorVersion = mBuffer.getInt();
+ final int minorVersion = mBuffer.getInt();
+ if (majorVersion != MAJOR_VERSION) {
+ throw new IOException("Incompatible version: "
+ + majorVersion + "." + minorVersion
+ + " in: " + filename);
+ }
+ }
+
+ private String readString() {
+ final int stringBytes = mBuffer.getInt();
+ mBuffer.limit(stringBytes + mBuffer.position());
+ final String result = CHARSET.decode(mBuffer).toString();
+ mBuffer.limit(mBuffer.capacity());
+ return result;
+ }
+
+ public LockingContext readContext(int id) {
+ mBuffer.position(id);
+ return new LockingContext(readString(),
+ readString(),
+ readString());
+ }
+
+ public Lock readLock(int id) {
+ mBuffer.position(id);
+ String className = readString();
+ int objectId = mBuffer.getInt();
+ return new Lock(className, objectId);
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.common.contexts;
+
+import TransactionalIO.core.TransactionalFile;
+import TransactionalIO.exceptions.GracefulException;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+//import net.jcip.annotations.ThreadSafe;
+
+import com.enea.jcarder.common.Lock;
+import com.enea.jcarder.common.LockingContext;
+import com.enea.jcarder.transactionalinterfaces.Bool;
+import com.enea.jcarder.transactionalinterfaces.Intif;
+import com.enea.jcarder.transactionalinterfaces.bytebuffer;
+import com.enea.jcarder.transactionalinterfaces.bytebuffer.byteholder;
+import com.enea.jcarder.util.logging.Logger;
+import dstm2.Init;
+import dstm2.atomic;
+import dstm2.Thread;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.nio.LongBuffer;
+import java.util.Vector;
+import java.util.concurrent.Callable;
+
+import dstm2.AtomicArray;
+
+//@ThreadSafe
+import java.util.logging.Level;
+public final class ContextFileWriter
+implements ContextWriterIfc {
+
+ private final FileChannel mChannel;
+ private int mNextFilePosition = 0;
+ private final Logger mLogger;
+ private ByteBuffer mBuffer;// = ByteBuffer.allocateDirect(8192);
+ private byte[] data;
+ RandomAccessFile raFile;
+
+
+ private bytebuffer mbuff;
+ private Intif filePosition;
+ private Intif test;
+ private Bool ShutdownHookExecuted;
+
+ TransactionalFile trraFile;
+
+
+ private boolean mShutdownHookExecuted = false;
+
+ public ContextFileWriter(Logger logger, File file) throws IOException {
+ System.out.println("d");
+ mbuff = new bytebuffer();
+ mbuff.allocateDirect(8192);
+ filePosition = new Intif();
+ filePosition.init();
+ test = new Intif();
+ test.init();
+ ShutdownHookExecuted = new Bool();
+ ShutdownHookExecuted.init();
+ data = new byte[8192];
+ mBuffer = ByteBuffer.wrap(data);
+ mLogger = logger;
+ mLogger.info("Opening for writing: " + file.getAbsolutePath());
+ raFile = new RandomAccessFile(file, "rw");
+
+ trraFile = new TransactionalFile(file.getAbsolutePath(), "rw");
+ trraFile.file.setLength(0);
+// ShutdownHookExecuted.init();
+
+
+ raFile.setLength(0);
+ mChannel = raFile.getChannel();
+
+
+ // writeHeader();
+
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ public void run() { shutdownHook(); }
+ });
+ }
+
+
+
+ private void shutdownHook() {
+ // System.out.println(Thread.currentThread() + " ashut ddddddborted in committing");
+ try{
+ Thread.doIt(new Callable<Boolean>() {
+
+ public Boolean call() {
+
+ try {
+ if (trraFile.file.getChannel().isOpen()) {
+ writeBuffer();
+
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ ShutdownHookExecuted.set(true);
+ return true;
+ }
+ });
+ }catch(GracefulException e){
+ // System.out.println(Thread.currentThread() + " shut graceful exc");
+ }
+ }
+
+
+
+ private void writeBuffer() throws IOException {
+ mbuff.flip();
+ System.out.println(Thread.currentThread());
+
+ // System.out.println(mbuff.remaining());
+ trraFile.write(mbuff.getBytes());
+ while (mbuff.hasRemaining()) {
+ Thread.yield();
+ trraFile.write(mbuff.getBytes());
+ }
+ mbuff.clear();
+ }
+
+
+
+ private void writeHeader() throws IOException {
+ // System.out.println("head");
+ ByteBuffer mBuffer = ByteBuffer.allocateDirect(16);
+ mBuffer.putLong(ContextFileReader.MAGIC_COOKIE);
+ mBuffer.putInt(ContextFileReader.MAJOR_VERSION);
+ mBuffer.putInt(ContextFileReader.MINOR_VERSION);
+ mBuffer.rewind();
+ for (int i=0; i<16; i++){
+ mbuff.put(mBuffer.get());
+ }
+ //filePosition.get();
+ test.increment(8+4+4);
+ //filePosition.increment(8+4+4);
+ }
+
+
+
+ public void close() throws IOException {
+ // System.out.println("clo");
+ try{
+ Thread.doIt(new Callable<Boolean>() {
+
+ public Boolean call() {
+ try {
+ // System.out.println(Thread.currentThread() + " closeaborted in committing");
+ writeBuffer();
+ trraFile.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return true;
+ };
+ });
+ }catch(GracefulException e){
+ System.out.println(Thread.currentThread() + " close graceful exc");
+ }
+ }
+
+
+
+ private void writeString(String s) throws IOException {
+ // System.out.println("str");
+ ByteBuffer encodedString = ContextFileReader.CHARSET.encode(s);
+ final int length = encodedString.remaining();
+ assureBufferCapacity(4 + length);
+ ByteBuffer mBuffer = ByteBuffer.allocateDirect(length +4);
+ mBuffer.putInt(length);
+ mBuffer.put(encodedString);
+ mBuffer.rewind();
+ System.out.println("---------------");
+ System.out.println("int: " + length);
+ System.out.println("str: " + encodedString);
+ System.out.println("---------------");
+ //test.increment(8+length);
+
+ //for (int i=0; i<4+length; i++)
+ // mbuff.put(mBuffer.get());
+ // while (mBuffer.hasRemaining())
+ // mbuff.put(mBuffer.get());
+ // mbuff.put(mBuffer);
+ // mbuff.put(encodedString);
+ //filePosition.get();
+ // test.increment(4+length);
+ // test.increment(4);
+ //filePosition.increment(4 + length);
+ }
+
+
+
+ private void writeInteger(int i) throws IOException {
+ // System.out.println("int");
+ assureBufferCapacity(4);
+ ByteBuffer mBuffer = ByteBuffer.allocateDirect(4);
+ mBuffer.putInt(i);
+ System.out.println("---------------");
+ System.out.println("int: " + i);
+
+
+ System.out.println("---------------");
+ for (int j=0; j<4; j++){
+ // mbuff.put(mBuffer.get());
+ }
+ // filePosition.get();
+ //test.increment(4);
+ //filePosition.increment(4);
+ }
+
+
+
+ private void assureBufferCapacity(int size) throws IOException {
+ if (mbuff.remaining() < size){// || ShutdownHookExecuted.isTrue()) {
+ writeBuffer();
+ }
+
+ // Grow buffer if it can't hold the requested size.
+ while (mbuff.capacity() < size) {
+ mbuff = mbuff.allocateDirect(2 * mbuff.capacity());
+ }
+ }
+
+
+
+
+
+
+ public int trwriteLock(Vector arg) throws IOException {
+ int startPosition = test.get();
+ //writeString(((Lock)arg.get(0)).getClassName());
+ //writeInteger(((Lock)arg.get(0)).getObjectId());
+
+ ByteBuffer encodedString = ContextFileReader.CHARSET.encode(((Lock)arg.get(0)).getClassName());
+ final int length = encodedString.remaining();
+ ByteBuffer mBuffer = ByteBuffer.allocateDirect(8+ length);
+ mBuffer.putInt(length);
+ mBuffer.put(encodedString);
+ mBuffer.putInt(((Lock)arg.get(0)).getObjectId());
+ mBuffer.rewind();
+ // assureBufferCapacity(8+length);
+ for (int j=0; j<(8+length); j++){
+ byte value = mBuffer.get();
+ //if (mbuff.mbuffer.getByteHolder().get(mbuff.mbuffer.getPosition()) == null)
+ // mbuff.mbuffer.getByteHolder().set(mbuff.mbuffer.getPosition(), mbuff.factory2.create());
+ AtomicArray<byteholder> ar = mbuff.mbuffer.getByteHolder();
+ byteholder bh = mbuff.factory2.create();
+ bh.setByte(value);
+ mbuff.mbuffer.getByteHolder().set(mbuff.mbuffer.getPosition(),bh);
+ mbuff.mbuffer.setPosition(mbuff.mbuffer.getPosition()+1);
+ }
+
+ /*System.out.println("---------------");
+ System.out.println("int: " + length);
+ System.out.println("str: " + encodedString);
+ System.out.println("int: " + ((Lock)arg.get(0)).getObjectId());
+ System.out.println("---------------");*/
+ test.increment(8+length);
+ flushBufferIfNeeded();
+ return startPosition;
+ }
+
+ public int writeLock(Lock lock) throws IOException {
+ // System.out.println("lock");
+ int result = 0;
+ try{
+ final Vector arg = new Vector() ;
+ arg.add(lock);
+ result = Thread.doIt(new Callable<Integer>() {
+
+ public Integer call() {
+ try {
+ // System.out.println(Thread.currentThread() + " alockborted in committing");
+ return trwriteLock(arg);
+ // System.out.println(Thread.currentThread() + " NO???? alockborted in committing");
+ } catch (IOException ex) {
+ java.util.logging.Logger.getLogger(ContextFileWriter.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ return -1;
+ }
+ });
+
+ }catch(GracefulException e){
+ System.out.println(Thread.currentThread() + "lock graceful exc");
+
+ }finally{
+ return result;
+ }
+ }
+
+
+
+ public int trwriteContext(LockingContext context)
+ throws IOException {
+ int startPosition = test.get();
+
+ // writeString(context.getThreadName());
+ // writeString(context.getLockReference());
+ // writeString(context.getMethodWithClass());
+
+ ByteBuffer encodedString = ContextFileReader.CHARSET.encode(context.getThreadName());
+ final int length = encodedString.remaining();
+ ByteBuffer encodedString2 = ContextFileReader.CHARSET.encode(context.getLockReference());
+ final int length2 = encodedString2.remaining();
+ ByteBuffer encodedString3 = ContextFileReader.CHARSET.encode(context.getMethodWithClass());
+ final int length3 = encodedString3.remaining();
+ ByteBuffer mBuffer = ByteBuffer.allocateDirect(12+length+length2+length3);
+
+ mBuffer.putInt(length);
+ mBuffer.put(encodedString);
+ mBuffer.putInt(length2);
+ mBuffer.put(encodedString2);
+ mBuffer.putInt(length3);
+ mBuffer.put(encodedString3);
+ mBuffer.rewind();
+ /* System.out.println("------");
+ System.out.println("int: " + length);
+ System.out.println("str: " + encodedString);
+ System.out.println("int: " + length2);
+ System.out.println("str: " + encodedString2);
+ System.out.println("int: " + length3);
+ System.out.println("str: " + encodedString3);
+ System.out.println("------");*/
+
+
+ // assureBufferCapacity(12 + length + length2 + length3);
+ for (int j=0;j<(12+length +length2 +length3);j++){
+ // mbuff.put(mBuffer.get());
+ byte value = mBuffer.get();
+ //if (mbuff.mbuffer.getByteHolder().get(mbuff.mbuffer.getPosition()) == null)
+ // mbuff.mbuffer.getByteHolder().set(mbuff.mbuffer.getPosition(), mbuff.factory2.create());
+ AtomicArray<byteholder> ar = mbuff.mbuffer.getByteHolder();
+ byteholder bh = mbuff.factory2.create();
+ bh.setByte(value);
+ mbuff.mbuffer.getByteHolder().set(mbuff.mbuffer.getPosition(),bh);
+ mbuff.mbuffer.getByteHolder().get(mbuff.mbuffer.getPosition()).setByte(value);
+ mbuff.mbuffer.setPosition(mbuff.mbuffer.getPosition()+1);
+ }
+
+ test.increment(12+length +length2 +length3);
+ flushBufferIfNeeded();
+
+ return startPosition;
+ }
+
+ public int writeContext(LockingContext context)
+ {
+
+ int startPosition = -2;
+ try{
+ final LockingContext c = context;
+ startPosition = Thread.doIt(new Callable<Integer>() {
+
+ public Integer call() throws IOException {
+ return trwriteContext(c);
+ }
+
+ });
+
+
+
+ }catch(GracefulException e){
+ System.out.println(Thread.currentThread() + " context graceful exc");
+ }
+ finally{
+ // System.out.println(Thread.currentThread() + " con");
+ return startPosition;
+ }
+ }
+
+ private void flushBufferIfNeeded() throws IOException {
+ if (ShutdownHookExecuted.isTrue()) {
+ // System.out.println("fdddlush");
+ writeBuffer();
+ }
+ }
+
+
+ /*
+ private void writeString(String s) throws IOException {
+
+ ByteBuffer encodedString = ContextFileReader.CHARSET.encode(s);
+ final int length = encodedString.remaining();
+
+ assureBufferCapacity(4 + length);
+ mBuffer.putInt(length);
+ mBuffer.put(encodedString);
+ System.out.println("------");
+ System.out.println("int: " + length);
+ System.out.println("str: " + encodedString);
+ System.out.println("------");
+
+ mNextFilePosition += 4 + length;
+ }
+
+ private synchronized void shutdownHook() {
+ System.out.println("shut");
+ try {
+ if (mChannel.isOpen()) {
+ writeBuffer();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ mShutdownHookExecuted = true;
+ }
+
+
+
+ public synchronized void close() throws IOException {
+ writeBuffer();
+ mChannel.close();
+ }
+
+ private void writeInteger(int i) throws IOException {
+ assureBufferCapacity(4);
+ mBuffer.putInt(i);
+ System.out.println("------");
+ System.out.println("int: " + i);
+ System.out.println("------");
+ mNextFilePosition += 4;
+
+ }
+ private void assureBufferCapacity(int size) throws IOException {
+ if (mBuffer.remaining() < size || mShutdownHookExecuted) {
+ writeBuffer();
+ }
+
+ // Grow buffer if it can't hold the requested size.
+ while (mBuffer.capacity() < size) {
+ mBuffer = ByteBuffer.allocateDirect(2 * mBuffer.capacity());
+ }
+ }
+
+ public synchronized int writeLock(Lock lock) throws IOException {
+ final int startPosition = mNextFilePosition;
+ writeString(lock.getClassName());
+ writeInteger(lock.getObjectId());
+ flushBufferIfNeeded();
+ return startPosition;
+ }
+
+ public synchronized int writeContext(LockingContext context)
+ throws IOException {
+ final int startPosition = mNextFilePosition;
+ writeString(context.getThreadName());
+ writeString(context.getLockReference());
+ writeString(context.getMethodWithClass());
+ flushBufferIfNeeded();
+ System.out.println(Thread.currentThread() + " con");
+ return startPosition;
+ }
+
+ private void flushBufferIfNeeded() throws IOException {
+ if (mShutdownHookExecuted) {
+ writeBuffer();
+ // System.out.println("sssskk");
+ }
+ }
+
+ private void writeBuffer() throws IOException {
+ mBuffer.flip();
+ System.out.println("here " + mBuffer.array().length);
+ System.out.println("Written" + mChannel.write(mBuffer));
+ while (mBuffer.hasRemaining()) {
+ Thread.yield();
+ raFile.write(data);
+ //mChannel.write(mBuffer);
+ }
+ mBuffer.clear();
+ }
+ private void writeHeader() throws IOException {
+ mBuffer.putLong(ContextFileReader.MAGIC_COOKIE);
+ mBuffer.putInt(ContextFileReader.MAJOR_VERSION);
+ mBuffer.putInt(ContextFileReader.MINOR_VERSION);
+ mNextFilePosition += 8 + 4 + 4;
+
+ // LongBuffer g;
+ // Long.
+ // Long.valueOf(ContextFileReader.MAGIC_COOKIE).
+ // mBuffer.putLong(ContextFileReader.MAGIC_COOKIE);
+ // mBuffer.putInt(ContextFileReader.MAJOR_VERSION);
+ // mBuffer.putInt(ContextFileReader.MINOR_VERSION);
+ // mNextFilePosition += 8 + 4 + 4;
+ }*/
+ }
+
+
+
+
+
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.common.contexts;
+
+import java.util.LinkedList;
+//import net.jcip.annotations.NotThreadSafe;
+import com.enea.jcarder.common.Lock;
+import com.enea.jcarder.common.LockingContext;
+
+//@NotThreadSafe
+public final class ContextMemory
+implements ContextWriterIfc, ContextReaderIfc {
+
+ private final LinkedList<Lock> mLocks = new LinkedList<Lock>();
+ private final LinkedList<LockingContext> mLockingContexts =
+ new LinkedList<LockingContext>();
+
+ public int writeLock(Lock lock) {
+ mLocks.addLast(lock);
+ return mLocks.size() - 1;
+ }
+
+ public int writeContext(LockingContext context) {
+ mLockingContexts.addLast(context);
+ return mLockingContexts.size() - 1;
+ }
+
+ public Lock readLock(int id) {
+ return mLocks.get(id);
+ }
+
+ public LockingContext readContext(int id) {
+ return mLockingContexts.get(id);
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.common.contexts;
+
+import com.enea.jcarder.common.Lock;
+import com.enea.jcarder.common.LockingContext;
+
+public interface ContextReaderIfc {
+ LockingContext readContext(int id);
+ Lock readLock(int id);
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.common.contexts;
+
+import java.io.IOException;
+
+import com.enea.jcarder.common.Lock;
+import com.enea.jcarder.common.LockingContext;
+
+public interface ContextWriterIfc {
+
+ int writeLock(Lock lock) throws IOException;
+
+ int writeContext(LockingContext context) throws IOException;
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.common.events;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+import com.enea.jcarder.util.logging.Logger;
+
+public final class EventFileReader {
+ private static final int INT_LENGTH = 4;
+ private static final int LONG_LENGTH = 8;
+ private final Logger mLogger;
+ static final int EVENT_LENGTH = (INT_LENGTH * 4) + LONG_LENGTH;
+ static final long MAGIC_COOKIE = 2153191828159737167L;
+ static final int MAJOR_VERSION = 1;
+ static final int MINOR_VERSION = 0;
+
+ public EventFileReader(Logger logger) {
+ mLogger = logger;
+ }
+
+ public void parseFile(File file,
+ LockEventListenerIfc eventReceiver)
+ throws IOException {
+ int numberOfParsedEvents = 0;
+ FileInputStream fis = new FileInputStream(file);
+ final String path = file.getCanonicalPath();
+ mLogger.info("Opening for reading: " + path);
+ FileChannel fileChannel = fis.getChannel();
+ validateHeader(fileChannel, path);
+ final ByteBuffer buffer = ByteBuffer.allocate(EVENT_LENGTH);
+ while (fileChannel.read(buffer) == EVENT_LENGTH) {
+ buffer.rewind();
+ parseLockEvent(buffer, eventReceiver);
+ buffer.rewind();
+ numberOfParsedEvents++;
+ }
+ mLogger.fine("Loaded " + numberOfParsedEvents
+ + " lock events from file.");
+ }
+
+ private void validateHeader(FileChannel channel,
+ String filename) throws IOException {
+ final ByteBuffer buffer = ByteBuffer.allocate(8 + 4 + 4);
+ channel.read(buffer);
+ buffer.flip();
+ if (MAGIC_COOKIE != buffer.getLong()) {
+ throw new IOException("Invalid file contents in: " + filename);
+ }
+ final int majorVersion = buffer.getInt();
+ final int minorVersion = buffer.getInt();
+ if (majorVersion != MAJOR_VERSION) {
+ throw new IOException("Incompatible version: "
+ + majorVersion + "." + minorVersion
+ + " in: " + filename);
+ }
+ }
+
+ private static void parseLockEvent(ByteBuffer lockEventBuffer,
+ LockEventListenerIfc eventReceiver)
+ throws IOException {
+ final int lockId = lockEventBuffer.getInt();
+ final int lockingContextId = lockEventBuffer.getInt();
+ final int lastTakenLockId = lockEventBuffer.getInt();
+ final int lastTakenLockingContextId = lockEventBuffer.getInt();
+ final long threadId = lockEventBuffer.getLong();
+ eventReceiver.onLockEvent(lockId,
+ lockingContextId,
+ lastTakenLockId,
+ lastTakenLockingContextId,
+ threadId);
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.common.events;
+
+import TransactionalIO.core.TransactionalFile;
+import TransactionalIO.exceptions.GracefulException;
+import com.enea.jcarder.transactionalinterfaces.Bool;
+import com.enea.jcarder.transactionalinterfaces.bytebuffer;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+//import net.jcip.annotations.ThreadSafe;
+
+import com.enea.jcarder.util.Counter;
+import com.enea.jcarder.util.TransactionalCounter;
+import com.enea.jcarder.util.logging.Logger;
+
+import java.util.concurrent.Callable;
+import static com.enea.jcarder.common.events.EventFileReader.EVENT_LENGTH;
+
+import dstm2.Thread;
+
+//@ThreadSafe
+import java.util.Vector;
+public final class EventFileWriter implements LockEventListenerIfc {
+ private final ByteBuffer mBuffer =
+ ByteBuffer.allocateDirect(EVENT_LENGTH * 1024);
+ private final FileChannel mFileChannel;
+ private final Logger mLogger;
+ private final Counter mWrittenLockEvents;
+ private boolean mShutdownHookExecuted = false;
+
+ private bytebuffer mbuff;
+ private Bool ShutdownHookExecuted;
+ TransactionalFile traf;
+ TransactionalCounter trmWrittenLockEvents;
+
+ public EventFileWriter(Logger logger, File file) throws IOException {
+
+
+
+ mLogger = logger;
+ mLogger.info("Opening for writing: " + file.getAbsolutePath());
+ RandomAccessFile raFile = new RandomAccessFile(file, "rw");
+ raFile.setLength(0);
+ mFileChannel = raFile.getChannel();
+ mWrittenLockEvents = new Counter("Written Lock Events",
+ mLogger,
+ 100000);
+
+ mbuff = new bytebuffer();
+ mbuff.allocateDirect(8192);
+ traf = new TransactionalFile(file.getAbsolutePath(), "rw");
+ ShutdownHookExecuted = new Bool();
+ ShutdownHookExecuted.init();
+ trmWrittenLockEvents = new TransactionalCounter("Written Lock Events",
+ mLogger,
+ 100000);
+
+ writeHeader();
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ public void run() { shutdownHook(); }
+ });
+ }
+
+ private void writeHeader() throws IOException {
+ mBuffer.putLong(EventFileReader.MAGIC_COOKIE);
+ mBuffer.putInt(EventFileReader.MAJOR_VERSION);
+ mBuffer.putInt(EventFileReader.MINOR_VERSION);
+ writeBuffer();
+ }
+
+ private void trwriteHeader() throws IOException {
+ ByteBuffer mBuffer = ByteBuffer.allocateDirect(16);
+ mBuffer.putLong(EventFileReader.MAGIC_COOKIE);
+ mBuffer.putInt(EventFileReader.MAJOR_VERSION);
+ mBuffer.putInt(EventFileReader.MINOR_VERSION);
+ mBuffer.rewind();
+
+ for (int i=0; i<16; i++){
+ mbuff.put(mBuffer.get());
+ }
+ writeBuffer();
+ }
+
+ public synchronized void onLockEvent(int lockId,
+ int lockingContextId,
+ int lastTakenLockId,
+ int lastTakenLockingContextId,
+ long threadId) throws IOException {
+
+ mBuffer.putInt(lockId);
+ mBuffer.putInt(lockingContextId);
+ mBuffer.putInt(lastTakenLockId);
+ mBuffer.putInt(lastTakenLockingContextId);
+ mBuffer.putLong(threadId);
+ mWrittenLockEvents.increment();
+ if (mBuffer.remaining() < EVENT_LENGTH || mShutdownHookExecuted) {
+ writeBuffer();
+ }
+ }
+
+ public void tronLockEvent(Vector arg) throws IOException {
+
+ ByteBuffer mBuffer = ByteBuffer.allocateDirect(24);
+ mBuffer.putInt((Integer)arg.get(0));
+ mBuffer.putInt((Integer)arg.get(1));
+ mBuffer.putInt((Integer)arg.get(2));
+ mBuffer.putInt((Integer)arg.get(3));
+ mBuffer.putLong((Long)arg.get(4));
+ mBuffer.rewind();
+
+ for (int i=0; i<24; i++){
+ mbuff.put(mBuffer.get());
+ }
+
+ trmWrittenLockEvents.increment();
+ if (mbuff.remaining() < EVENT_LENGTH || ShutdownHookExecuted.isTrue()) {
+ writeBuffer();
+ }
+ }
+
+ public void trLockEventWrapper(int lockId,
+ int lockingContextId,
+ int lastTakenLockId,
+ int lastTakenLockingContextId,
+ long threadId) throws IOException{
+
+
+ final Vector arg = new Vector();
+ arg.add(Integer.valueOf(lockId));
+ arg.add(Integer.valueOf(lockingContextId));
+ arg.add(Integer.valueOf(lastTakenLockId));
+ arg.add(Integer.valueOf(lastTakenLockingContextId));
+ arg.add(Long.valueOf(threadId));
+ Thread.doIt(new Callable<Boolean>() {
+
+ public Boolean call() throws IOException {
+ tronLockEvent(arg);
+ return true;
+ }
+
+ });
+
+ }
+
+ private void writeBuffer() throws IOException {
+ mBuffer.flip();
+ mFileChannel.write(mBuffer);
+ while (mBuffer.hasRemaining()) {
+ Thread.yield();
+ mFileChannel.write(mBuffer);
+ }
+ mBuffer.clear();
+ }
+
+
+ private void trwriteBuffer() throws IOException {
+ mbuff.flip();
+ traf.write(mbuff.getBytes());
+ while (mbuff.hasRemaining()) {
+ Thread.yield();
+ traf.write(mbuff.getBytes());
+ }
+ mbuff.clear();
+ }
+
+
+ public synchronized void close() throws IOException {
+
+ writeBuffer();
+ mFileChannel.close();
+ }
+
+ public void trclose() throws IOException {
+ // System.out.println("clo");
+ try{
+ Thread.doIt(new Callable<Boolean>() {
+
+ public Boolean call() {
+ try {
+ // System.out.println(Thread.currentThread() + " closeaborted in committing");
+ writeBuffer();
+ traf.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return true;
+ };
+ });
+ }catch(GracefulException e){
+ System.out.println(Thread.currentThread() + " close graceful exc");
+ }
+ }
+
+ private synchronized void shutdownHook() {
+ try {
+ if (mFileChannel.isOpen()) {
+ writeBuffer();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ mShutdownHookExecuted = true;
+ }
+
+
+ private void trshutdownHook() {
+ System.out.println(Thread.currentThread() + " ashut ddddddborted in committing");
+ try{
+ Thread.doIt(new Callable<Boolean>() {
+
+ public Boolean call() {
+
+ try {
+ if (traf.file.getChannel().isOpen()) {
+ writeBuffer();
+
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ ShutdownHookExecuted.set(true);
+ return true;
+ }
+ });
+ }catch(GracefulException e){
+ System.out.println(Thread.currentThread() + " shut graceful exc");
+ }
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.common.events;
+
+import java.io.IOException;
+import java.util.Vector;
+
+public interface LockEventListenerIfc {
+
+ void onLockEvent(int lockId,
+ int lockingContextId,
+ int lastTakenLockId,
+ int lastTakenLockingContextId,
+ long threadId)throws IOException;
+
+ void tronLockEvent(Vector srgs)throws IOException;
+}
+
+
+
--- /dev/null
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package com.enea.jcarder.transactionalinterfaces;
+
+/**
+ *
+ * @author navid
+ */
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+
+import com.enea.jcarder.transactionalinterfaces.bytebuffer.byteholder;
+import dstm2.AtomicArray;
+import dstm2.atomic;
+import dstm2.Thread;
+import dstm2.factory.Factory;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+
+/**
+ *
+ * @author navid
+ */
+public class Bool {
+
+
+ static Factory<boolif> factory = Thread.makeFactory(boolif.class);
+ boolif boolif;
+
+ public void init(){
+ boolif = factory.create();
+ boolif.setValue(false);
+ }
+
+ public void set(boolean v){
+ boolif.setValue(v);
+ }
+
+ public boolean isTrue(){
+ return boolif.getValue();
+ }
+
+
+
+ @atomic public interface boolif{
+ boolean getValue();
+ void setValue(boolean value);
+
+ // void setLong(long value);
+ }
+
+
+
+}
--- /dev/null
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package com.enea.jcarder.transactionalinterfaces;
+
+import dstm2.atomic;
+import dstm2.Thread;
+import dstm2.factory.Factory;
+
+/**
+ *
+ * @author navid
+ */
+public class Intif {
+
+ public static Factory<positionif> factory = Thread.makeFactory(positionif.class);
+
+ positionif pos;
+
+ public void init(){
+
+ pos = factory.create();
+ //pos.setPosition(0);
+ }
+
+ public void increment(int offset){
+ pos.setPosition(pos.getPosition() + offset);
+
+ }
+
+ public int get(){
+ return pos.getPosition();
+ }
+
+ @atomic public interface positionif{
+ public int getPosition();
+ public void setPosition(int pos);
+ }
+}
--- /dev/null
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package com.enea.jcarder.transactionalinterfaces;
+
+import com.enea.jcarder.transactionalinterfaces.bytebuffer.byteholder;
+import dstm2.AtomicArray;
+import dstm2.atomic;
+import dstm2.Thread;
+import dstm2.factory.Factory;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+
+/**
+ *
+ * @author navid
+ */
+public class bytebuffer {
+
+
+ static Factory<bytebufferif> factory = Thread.makeFactory(bytebufferif.class);
+ public static Factory<byteholder> factory2 = Thread.makeFactory(byteholder.class);
+
+
+ public bytebufferif mbuffer;
+
+ public int capacity(){
+ return mbuffer.getCapacity();
+ }
+
+ public int position(){
+ return mbuffer.getPosition();
+ }
+
+ public bytebufferif flip(){
+ mbuffer.setLimit(mbuffer.getPosition());
+ mbuffer.setPosition(0);
+ return mbuffer;
+ }
+
+ public final bytebufferif clear(){
+ mbuffer.setPosition(0);
+ mbuffer.setLimit(mbuffer.getCapacity());
+ return mbuffer;
+ }
+
+ public final boolean hasRemaining(){
+ return mbuffer.getPosition() < mbuffer.getLimit();
+ }
+
+ public final int remaining(){
+ return mbuffer.getLimit() - mbuffer.getPosition();
+ }
+
+ public void put(byte value){
+ if (mbuffer.getByteHolder().get(mbuffer.getPosition()) == null)
+ mbuffer.getByteHolder().set(mbuffer.getPosition(), factory2.create());
+ mbuffer.getByteHolder().get(mbuffer.getPosition()).setByte(value);
+ mbuffer.setPosition(mbuffer.getPosition()+1);
+ }
+
+ public void put(ByteBuffer value){
+
+ if (remaining() < value.remaining())
+ throw new BufferOverflowException();
+
+ for (int i=0; i<value.remaining(); i++){
+ if (mbuffer.getByteHolder().get(mbuffer.getPosition()) == null)
+ mbuffer.getByteHolder().set(mbuffer.getPosition(), factory2.create());
+ mbuffer.getByteHolder().get(mbuffer.getPosition()).setByte(value.get());
+ mbuffer.setPosition(mbuffer.getPosition()+1);
+ //System.out.println("sss");
+ }
+ }
+
+
+
+ public bytebuffer allocateDirect(int capacity){
+ mbuffer = factory.create();
+ mbuffer.setByteHolder(new AtomicArray<byteholder>(byteholder.class,capacity));
+ mbuffer.setPosition(0);
+ mbuffer.setLimit(capacity);
+ mbuffer.setCapacity(capacity);
+ AtomicArray<byteholder> ar = mbuffer.getByteHolder();
+ //for (int i=0; i<capacity; i++){
+ // ar.set(i, factory2.create());
+ //}
+ // for (int i=0; i<capacity; i++)
+ // mbuffer.getByteHolder().set(i, factory2.create());
+ // mbuffer.getByteHolder().set(0, factory2.create());
+ // mbuffer.getByteHolder().get(0).setByte((byte)2);
+ return this;
+ }
+
+ public byte[] getBytes(){
+ int length = remaining();
+ byte[] result = new byte[length];
+ int i = 0;
+ while (hasRemaining()) {
+ result[i] = mbuffer.getByteHolder().get(mbuffer.getPosition()).getByte();
+ mbuffer.setPosition(mbuffer.getPosition()+1);
+ i++;
+ }
+ return result;
+ }
+
+ @atomic public interface bytebufferif{
+ int getLimit();
+ void setLimit(int value);
+ int getPosition();
+ void setPosition(int value);
+ int getCapacity();
+ void setCapacity(int value);
+ AtomicArray<byteholder> getByteHolder();
+ void setByteHolder(AtomicArray<byteholder> bytes);
+ }
+
+ @atomic public interface byteholder{
+ byte getByte();
+ void setByte(byte value);
+ }
+
+
+
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.util;
+
+import java.io.IOException;
+import java.util.Properties;
+
+public final class BuildInformation {
+
+ private BuildInformation() { }
+
+ public static String getShortInfo() {
+ try {
+ Properties props = loadBuildProperties();
+ return "JCarder ("
+ + props.getProperty("build.version")
+ + "/"
+ + props.getProperty("build.number")
+ + ")";
+ } catch (IOException e) {
+ e.printStackTrace();
+ return "JCarder";
+ }
+ }
+
+ public static void printLongBuildInformation() {
+ Properties props;
+ try {
+ props = loadBuildProperties();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return;
+ }
+ StringBuffer sb = new StringBuffer();
+ sb.append("JCarder -- cards Java programs to keep threads"
+ + " disentangled\n");
+ sb.append("\nCopyright (C) 2006-2007 Enea AB\n");
+ sb.append("Copyright (C) 2007 Ulrik Svensson\n");
+ sb.append("Copyright (C) 2007 Joel Rosdahl\n");
+ sb.append("\nVersion: " + props.getProperty("build.version"));
+ sb.append("\nBuild : " + props.getProperty("build.number"));
+ sb.append("\nAt : " + props.getProperty("build.timestamp"));
+ sb.append("\nBy : " + props.getProperty("build.user.name"));
+ sb.append("\nOn : " + props.getProperty("build.os.name"));
+ System.out.println(sb.toString());
+ }
+
+ private static Properties loadBuildProperties() throws IOException {
+ Properties props = new Properties();
+ ClassLoader classLoader = ClassLoader.getSystemClassLoader();
+ props.load(classLoader.getResourceAsStream("build.properties"));
+ return props;
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.util;
+
+import com.enea.jcarder.util.logging.Logger;
+
+//import net.jcip.annotations.NotThreadSafe;
+
+//@NotThreadSafe
+public final class Counter {
+ final int mLogIntervall;
+ final String mName;
+ final Logger mLogger;
+ int mValue = 0;
+
+ public Counter(String name, Logger logger, int logInterval) {
+ mName = name;
+ mLogger = logger;
+ mLogIntervall = logInterval;
+ }
+
+ public void increment() {
+ mValue++;
+ if ((mValue % mLogIntervall) == 0) {
+ mLogger.fine(mName + ": " + mValue);
+ } else if (mLogger.isLoggable(Logger.Level.FINEST)) {
+ mLogger.finest(mName + ": " + mValue);
+ }
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.util;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+//import net.jcip.annotations.NotThreadSafe;
+
+/**
+ * This class is similar to the java.util.WeakHashMap but compares objects with
+ * the == operator instead of with the Object.equals method.
+ *
+ * TODO Add basic tests for this class.
+ */
+//@NotThreadSafe
+public final class IdentityWeakHashMap<V> {
+ private final HashMap<IdentityComparableKey, V> mHashMap;
+ private final ReferenceQueue<Object> mReferenceQueue;
+ private final StrongKey mStrongKey = new StrongKey();
+ private Object mLastKey = null; // Cache to improve performance.
+ private V mLastValue = null; // Cache to improve performance.
+ private int mPutCounter = 0;
+
+ public IdentityWeakHashMap() {
+ mHashMap = new HashMap<IdentityComparableKey, V>();
+ mReferenceQueue = new ReferenceQueue<Object>();
+ }
+
+ public V get(Object key) {
+ /*
+ * Avoid calls to removeGarbageCollectedKeys in this method in order to
+ * improve performance.
+ */
+ if (key == mLastKey) {
+ return mLastValue;
+ }
+ mLastKey = key;
+ mStrongKey.setReferent(key);
+ mLastValue = mHashMap.get(mStrongKey);
+ return mLastValue;
+ }
+
+ public void put(Object key, V value) {
+ assert value != null;
+ mLastKey = key;
+ mLastValue = value;
+ if (mPutCounter > 1000) {
+ // Don't call to often in order to improve performance.
+ removeGarbageCollectedKeys();
+ mPutCounter = 0;
+ } else {
+ mPutCounter++;
+ }
+ mHashMap.put((new WeakKey(key, mReferenceQueue)), value);
+ }
+
+ private void removeGarbageCollectedKeys() {
+ Reference e;
+ int noOfCollectedLocks = 0;
+ while ((e = mReferenceQueue.poll()) != null) {
+ noOfCollectedLocks++;
+ mHashMap.remove(e);
+ }
+ }
+
+ private static interface IdentityComparableKey {
+ Object get();
+ boolean equals(Object obj);
+ int hashCode();
+ }
+
+ private static class StrongKey implements IdentityComparableKey {
+ private Object mReferent;
+
+ void setReferent(Object referent) {
+ assert referent != null;
+ mReferent = referent;
+ }
+
+ public Object get() {
+ return mReferent;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ try {
+ IdentityComparableKey reference = (IdentityComparableKey) obj;
+ return (reference.get() == get());
+ } catch (ClassCastException e) {
+ return false;
+ }
+ }
+
+ public int hashCode() {
+ return System.identityHashCode(mReferent);
+ }
+ }
+
+ private static class WeakKey extends WeakReference<Object>
+ implements IdentityComparableKey {
+ private final int mHash;
+
+ WeakKey(Object referent, ReferenceQueue<Object> queue) {
+ super(referent, queue);
+ assert referent != null;
+ mHash = System.identityHashCode(referent);
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ try {
+ IdentityComparableKey reference = (IdentityComparableKey) obj;
+ Object referent = reference.get();
+ return (referent != null) && (referent == get());
+ } catch (ClassCastException e) {
+ return false;
+ }
+ }
+
+ public int hashCode() {
+ return mHash;
+ }
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.util;
+
+public class InvalidOptionException extends Exception {
+ public InvalidOptionException(String message) {
+ super(message);
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.util;
+
+import com.enea.jcarder.util.logging.Logger;
+
+//import net.jcip.annotations.NotThreadSafe;
+
+//@NotThreadSafe
+public final class MaxValueCounter {
+ final String mName;
+ final Logger mLogger;
+ int mValue = 0;
+ int mMaxValue = 0;
+
+ public MaxValueCounter(String name, Logger logger) {
+ mName = name;
+ mLogger = logger;
+ }
+
+ public void set(int value) {
+ mValue = value;
+ if (mValue > mMaxValue) {
+ mMaxValue = mValue;
+ mLogger.fine("New " + mName + ": " + mMaxValue);
+ }
+ }
+
+ public String toString() {
+ return String.valueOf(mMaxValue);
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is a helper class for an OptionParser.
+ */
+class OptionFormatter {
+ private final int mOptionIndent;
+ private final int mDescrIndent;
+ private final int mMaxWidth;
+
+ public OptionFormatter(int optionIndent, int descrIndent, int maxWidth) {
+ mOptionIndent = optionIndent;
+ mDescrIndent = descrIndent;
+ mMaxWidth = maxWidth;
+ }
+
+ public void format(StringBuilder sb, String option, String descr) {
+ addSpace(sb, mOptionIndent);
+ sb.append(option);
+ final int firstIndent;
+ if (mOptionIndent + option.length() >= mDescrIndent) {
+ sb.append("\n");
+ firstIndent = mDescrIndent;
+ } else {
+ firstIndent = mDescrIndent - (mOptionIndent + option.length());
+ }
+ if (descr == null) {
+ sb.append('\n');
+ } else {
+ List<String> descrLines = wrapText(descr, mMaxWidth - mDescrIndent);
+ indentText(sb, descrLines, mDescrIndent, firstIndent);
+ }
+ }
+
+ static void addSpace(StringBuilder sb, int n) {
+ for (int i = 0; i < n; ++i) {
+ sb.append(' ');
+ }
+ }
+
+ static List<String> wrapText(String text, int maxWidth) {
+ ArrayList<String> result = new ArrayList<String>();
+ int pos = 0;
+ StringBuilder sb = new StringBuilder();
+
+ for (String word : text.split("\\s+")) {
+ if (sb.length() > 0 && word.length() + 1 > maxWidth - sb.length()) {
+ result.add(sb.toString());
+ sb.setLength(0);
+ sb.append(word);
+ pos = word.length();
+ } else {
+ if (sb.length() > 0) {
+ sb.append(' ');
+ }
+ sb.append(word);
+ pos += word.length();
+ }
+ }
+ if (sb.length() > 0) {
+ result.add(sb.toString());
+ }
+
+ return result;
+ }
+
+ static void indentText(StringBuilder sb,
+ List<String> lines,
+ int indent,
+ int firstIndent) {
+ boolean first = true;
+ addSpace(sb, firstIndent);
+ for (String line : lines) {
+ if (first) {
+ first = false;
+ } else {
+ addSpace(sb, indent);
+ }
+ sb.append(line);
+ sb.append('\n');
+ }
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class parses command-line options.
+ */
+public class OptionParser {
+ private class OptionData {
+ String valueName;
+ String description;
+ }
+
+ private final List<String> mArguments = new ArrayList<String>();
+ private final Map<String, String> mParsedOptions =
+ new HashMap<String, String>();
+ private final Map<String, OptionData> mValidOptions =
+ new HashMap<String, OptionData>();
+
+ public OptionParser() {
+ }
+
+ /**
+ * Tell the parser to recognize a new option.
+ *
+ * Options can be given on the following forms:
+ *
+ * <ul>
+ * <li>-option</li>
+ * <li>-option foo</li>
+ * </ul>
+ *
+ * The first form specifies an option without a value and the second
+ * specifies an option with a value.
+ *
+ * @param option
+ * The option.
+ * @param description
+ * Description of the option.
+ */
+ public void addOption(String option, String description) {
+ final int spacePos = option.indexOf(' ');
+ final String flag;
+ final String valueName;
+ if (spacePos == -1) {
+ flag = option;
+ valueName = null;
+ } else {
+ flag = option.substring(0, spacePos);
+ valueName = option.substring(spacePos + 1);
+ }
+ OptionData data = new OptionData();
+ data.valueName = valueName;
+ data.description = description;
+ mValidOptions.put(flag, data);
+ }
+
+ /**
+ * Get arguments remaining after options (and their values) have been
+ * parsed.
+ *
+ * @return The arguments.
+ */
+ public List<String> getArguments() {
+ return mArguments;
+ }
+
+ /**
+ * Get a string containing help text describing available options.
+ *
+ * @return The help text.
+ */
+ public String getOptionHelp() {
+ StringBuilder sb = new StringBuilder();
+ OptionFormatter formatter = new OptionFormatter(2, 23, 79);
+ ArrayList<String> options = new ArrayList<String>();
+ options.addAll(mValidOptions.keySet());
+ Collections.sort(options);
+ for (String option : options) {
+ final OptionData data = mValidOptions.get(option);
+ final String optAndVal;
+ if (data.valueName == null) {
+ optAndVal = option;
+ } else {
+ optAndVal = option + " " + data.valueName;
+ }
+ formatter.format(sb, optAndVal, data.description);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Get parsed options.
+ *
+ * The map is keyed on option and the values are the option values (null if
+ * no parameter was expected).
+ *
+ * @return The option map.
+ */
+ public Map<String, String> getOptions() {
+ return mParsedOptions;
+ }
+
+ /**
+ * Parse arguments.
+ *
+ * @param arguments
+ * The arguments to parse.
+ * @throws InvalidOptionException
+ * If an invalid option is encountered.
+ */
+ public void parse(String[] arguments) throws InvalidOptionException {
+ mArguments.clear();
+ boolean reachedNonOption = false;
+ for (int i = 0; i < arguments.length; ++i) {
+ if (!reachedNonOption
+ && (arguments[i].length() == 0
+ || arguments[i].charAt(0) != '-')) {
+ reachedNonOption = true;
+ }
+ if (reachedNonOption) {
+ mArguments.add(arguments[i]);
+ } else {
+ String option = arguments[i];
+ if (mValidOptions.containsKey(option)) {
+ OptionData data = mValidOptions.get(option);
+ String optionArgument;
+ if (data.valueName != null) {
+ ++i;
+ if (i < arguments.length) {
+ optionArgument = arguments[i];
+ } else {
+ String message = "value missing to flag " + option;
+ throw new InvalidOptionException(message);
+ }
+ } else {
+ optionArgument = null;
+ }
+ mParsedOptions.put(option, optionArgument);
+ } else {
+ throw new InvalidOptionException("invalid flag: "
+ + arguments[i]);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package com.enea.jcarder.util;
+
+import com.enea.jcarder.transactionalinterfaces.Intif;
+import com.enea.jcarder.util.logging.Logger;
+
+
+/**
+ *
+ * @author navid
+ */
+public class TransactionalCounter {
+
+ final int mLogIntervall;
+ final String mName;
+ final Logger mLogger;
+ Intif.positionif mValue;
+
+ public TransactionalCounter(String name, Logger logger, int logInterval) {
+ mValue = Intif.factory.create();
+ mValue.setPosition(0);
+ mName = name;
+ mLogger = logger;
+ mLogIntervall = logInterval;
+ }
+
+ public void increment() {
+ mValue.setPosition(mValue.getPosition()+1);
+ if (((mValue.getPosition()) % mLogIntervall) == 0) {
+ mLogger.fine(mName + ": " + mValue);
+ } else if (mLogger.isLoggable(Logger.Level.FINEST)) {
+ mLogger.finest(mName + ": " + mValue);
+ }
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.util.logging;
+
+import TransactionalIO.core.TransactionalFile;
+import java.io.IOException;
+
+import com.enea.jcarder.util.logging.Logger.Level;
+
+/**
+ * This class acts as a log handler for classes that that implement the
+ * Appendable interface.
+ *
+ * The Appendable does not need to be thread-safe; AppendableHandler
+ * synchronizes calls to Appendable's methods.
+ */
+public class AppendableHandler implements Handler {
+ private final Appendable mDestination;
+ private final Level mLevel;
+ private final String mMessageFormat;
+ TransactionalFile traf;
+
+ public AppendableHandler(Appendable mDestination, TransactionalFile traf) {
+ this(mDestination, Logger.Level.FINEST, traf);
+ }
+
+
+ public AppendableHandler(Appendable mDestination, Level mLevel, TransactionalFile traf) {
+ this(mDestination, mLevel, "{level}: {message}\n", traf);
+ }
+
+
+
+ /**
+ * Constructor.
+ *
+ * The default log level is FINEST and the default message format is
+ * "{level}: {message}\n"
+ *
+ * @param destination
+ * Destination of the log messages.
+ */
+
+
+
+ public AppendableHandler(Appendable destination) {
+ this(destination, Logger.Level.FINEST);
+ }
+
+ /**
+ * Constructor.
+ *
+ * The default message format is "{level}: {message}\n"
+ *
+ * @param destination
+ * Destination of the log messages.
+ * @param logLevel
+ * Log level.
+ */
+ public AppendableHandler(Appendable destination, Logger.Level logLevel) {
+ this(destination, logLevel, "{level}: {message}\n");
+ }
+
+ /**
+ * Constructor.
+ *
+ * Substrings like {keyword} are expanded in the message format string.
+ *
+ * Currently supported keywords:
+ *
+ * - {message} -- the message
+ * - {level} -- the log level
+ *
+ * @param destination Destination of the log messages.
+ * @param logLevel Log level.
+ * @param messageFormat Message format.
+ */
+ public AppendableHandler(Appendable destination,
+ Logger.Level logLevel,
+ String messageFormat) {
+ mDestination = destination;
+ mLevel = logLevel;
+ mMessageFormat = messageFormat;
+ }
+
+ public AppendableHandler(Appendable mDestination, Level mLevel, String mMessageFormat, TransactionalFile traf) {
+ this.mDestination = mDestination;
+ this.mLevel = mLevel;
+ this.mMessageFormat = mMessageFormat;
+ this.traf = traf;
+ }
+
+
+
+
+ public void publish(Level level, String message) {
+ if (level.compareTo(mLevel) <= 0) {
+ try {
+ String formattedMessage = mMessageFormat
+ .replace("{level}", level.toString())
+ .replace("{message}", message);
+ synchronized (mDestination) {
+ mDestination.append(formattedMessage);
+ }
+ //traf.write(formattedMessage.getBytes());
+ } catch (IOException e) {
+ // Ignore.
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.util.logging;
+
+import com.enea.jcarder.util.logging.Logger.Level;
+
+/**
+ * This interface must be implemented by classes that handles log messages from
+ * the Logger class.
+ */
+public interface Handler {
+ /**
+ * Handle a published message.
+ *
+ * This method is called by a Logger class each time it receives a message
+ * to be logged.
+ *
+ * @param level Log level of the message.
+ * @param message The message.
+ */
+ void publish(Level level, String message);
+}
--- /dev/null
+/*
+ * JCarder -- cards Java programs to keep threads disentangled
+ *
+ * Copyright (C) 2006-2007 Enea AB
+ * Copyright (C) 2007 Ulrik Svensson
+ * Copyright (C) 2007 Joel Rosdahl
+ *
+ * This program is made available under the GNU GPL version 2, with a special
+ * exception for linking with JUnit. See the accompanying file LICENSE.txt for
+ * details.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package com.enea.jcarder.util.logging;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * A simple logging framework.
+ *
+ * We use our own simple Logging framework for several reasons:
+ * - To avoid interfering with logging from the user application. Even if we
+ * were using Log4J instead of java.util.Logging.*, JCarder might interfere with
+ * for example Log4J system properties.
+ * - The default java.util.logging.LogManager is reset by a shutdown hook and
+ * there is no fixed order in which the shutdown hooks are executed.
+ * - Minimizing the usage of the Java standard library improves performance and
+ * minimizes the risk of deadlock if the standard library is instrumented by
+ * JCarder.
+ */
+public final class Logger {
+ public static enum Level {
+ SEVERE,
+ WARNING,
+ INFO,
+ CONFIG,
+ FINE,
+ FINER,
+ FINEST;
+
+ /**
+ * Parse a string and return the corresponding log level.
+ *
+ * @param string The string to parse.
+ * @return
+ */
+ public static Level fromString(String string) {
+ for (Level level : values()) {
+ if (string.equalsIgnoreCase(level.toString())) {
+ return level;
+ }
+ }
+ return null;
+ }
+
+ public static String getEnumeration() {
+ StringBuffer sb = new StringBuffer();
+ boolean first = true;
+ for (Level level : values()) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(", ");
+ }
+ sb.append(level.toString());
+ }
+ return sb.toString();
+ }
+ }
+
+ final Collection<Handler> mHandlers;
+ final Level mLevel;
+
+ /**
+ * Constructor setting default value of log level.
+ *
+ * The default is to log everything, i.e., log level FINEST.
+ *
+ * @param handlers Log handlers. null means no handlers.
+ */
+ public Logger(Collection<Handler> handlers) {
+ this(handlers, Level.FINEST);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param handlers Log handlers. null means no handlers.
+ * @param logLevel Log level.
+ */
+ public Logger(Collection<Handler> handlers, Level logLevel) {
+ mLevel = logLevel;
+ mHandlers = new ArrayList<Handler>();
+ if (handlers != null) {
+ // Create a copy of the provided collection instead of sharing
+ // it, in order to make sure that mHandlers is immutable and
+ // thread safe.
+ mHandlers.addAll(handlers);
+ }
+ }
+
+ /**
+ * Check whether a message with a certain level would be logged.
+ *
+ * @param level The level.
+ * @return True if the message would be logged, otherwise false.
+ */
+ public boolean isLoggable(Level level) {
+ return level.compareTo(mLevel) <= 0;
+ }
+
+ /**
+ * Log a message with level SEVERE.
+ *
+ * @param message The message.
+ */
+ public void severe(String message) {
+ publishLog(Level.SEVERE, message);
+ }
+
+
+ /**
+ * Log a message with level WARNING.
+ *
+ * @param message The message.
+ */
+ public void warning(String message) {
+ publishLog(Level.WARNING, message);
+ }
+
+
+ /**
+ * Log a message with level INFO.
+ *
+ * @param message The message.
+ */
+ public void info(String message) {
+ publishLog(Level.INFO, message);
+ }
+
+
+ /**
+ * Log a message with level CONFIG.
+ *
+ * @param message The message.
+ */
+ public void config(String message) {
+ publishLog(Level.CONFIG, message);
+ }
+
+
+ /**
+ * Log a message with level FINE.
+ *
+ * @param message The message.
+ */
+ public void fine(String message) {
+ publishLog(Level.FINE, message);
+ }
+
+
+ /**
+ * Log a message with level FINER.
+ *
+ * @param message The message.
+ */
+ public void finer(String message) {
+ publishLog(Level.FINER, message);
+ }
+
+ /**
+ * Log a message with level FINEST.
+ *
+ * @param message The message.
+ */
+ public void finest(String message) {
+ publishLog(Level.FINEST, message);
+ }
+
+ private void publishLog(Level level, String message) {
+ if (isLoggable(level)) {
+ for (Handler handler : mHandlers) {
+ handler.publish(level, message);
+ }
+ }
+ }
+}