*** empty log message ***
diff --git a/bundles/org.eclipse.core.runtime/META-INF/MANIFEST.MF b/bundles/org.eclipse.core.runtime/META-INF/MANIFEST.MF index 6702cf7..2df3e11 100644 --- a/bundles/org.eclipse.core.runtime/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.core.runtime/META-INF/MANIFEST.MF
@@ -23,6 +23,7 @@ org.eclipse.core.internal.boot Import-Package: org.osgi.framework, + org.eclipse.osgi.service.debug, org.eclipse.osgi.service.environment, org.osgi.service.packageadmin, org.osgi.service.url,
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/DeadlockDetector.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/DeadlockDetector.java index 15acbef..98742e0 100644 --- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/DeadlockDetector.java +++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/DeadlockDetector.java
@@ -1,564 +1,656 @@ -/******************************************************************************* - * Copyright (c) 2003 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Common Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/cpl-v10.html - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.core.internal.jobs; - -import java.util.ArrayList; -import java.util.Arrays; -import org.eclipse.core.internal.runtime.*; -import org.eclipse.core.runtime.*; -import org.eclipse.core.runtime.jobs.ILock; -import org.eclipse.core.runtime.jobs.ISchedulingRule; - -class DeadlockDetector { - private static int NO_STATE = 0; - //state variables in the graph - private static int WAITING_FOR_LOCK = -10; - //whether deadlock exists between the threads that are in the graph - private boolean deadlockExists = false; - //matrix of relationships between threads and locks - private int[][] graph = new int[0][0]; - //index is column in adjacency matrix for the lock - private final ArrayList locks = new ArrayList(); - //index is row in adjacency matrix for the thread - private final ArrayList lockThreads = new ArrayList(); - //whether the graph needs to be resized - private boolean resize = false; - - /** - * Add the thread that owns the given lock to the list of threads that are - * waiting for some action. (these threads cannot continue until the lock - * they are waiting for is free) - */ - private boolean addWaitingThread(int[] waitingThreads, int column) { - boolean found = false; - int index = -1; - //find the thread that owns the lock with the given index - //(go through all the threads in the graph as a type of error - // checking) - for (int i = 0; i < graph.length; i++) { - if (graph[i][column] > NO_STATE) { - Assert.isTrue(!found, "The lock " + locks.get(column).toString() + " is owned by 2 threads."); //$NON-NLS-1$ //$NON-NLS-2$ - found = true; - index = i; - } - } - //matrix could be in an unsynchronized state so a lock could be - // unowned (index of -1) - if (index < NO_STATE) - return false; - if (waitingThreads[index] > NO_STATE) - deadlockExists = true; - waitingThreads[index] = 1; - return true; - } - /** - * Get the thread that owns the lock this thread is waiting for. - */ - private Thread blockingThread(Thread current) { - ISchedulingRule lock = (ISchedulingRule) getWaitingLock(current); - return getThreadOwningLock(lock); - } - /** - * Check that the addition of a waiting thread did not pruduce deadlock. If - * deadlock is detected, the deadlockExists variable is set to true. - */ - private void checkWaitCycles(int[] waitingThreads, int column) { - if (!addWaitingThread(waitingThreads, column)) - return; - if (deadlockExists) - return; - - for (int i = 0; i < graph.length; i++) { - if (graph[i][column] > NO_STATE) { - for (int j = 0; j < graph[i].length; j++) { - if (graph[i][j] == WAITING_FOR_LOCK) { - checkWaitCycles(waitingThreads, j); - } - if (deadlockExists) - break; - } - } - } - } - /** - * Combine the entries for two conflicting rule columns into a single column. - */ - private void combineColumns(int from, int to) { - for (int i = 0; i < graph.length; i++) { - if ((graph[i][to] != 0) && (graph[i][from] != 0)) - Assert.isLegal(false, "Incorrect Graph."); //$NON-NLS-1$ - - graph[i][to] += graph[i][from]; - graph[i][from] = NO_STATE; - } - removeExtraRows(0, from); - } - /** - * Returns true IFF the matrix contains a row for the given thread. - * (meaning the given thread either owns locks or is waiting for locks) - */ - boolean contains(Thread t) { - return lockThreads.contains(t); - } - /** - * Returns an array of contested locks that are owned by the given thread. - * Contested locks are locks that are owned by this thread, but have other - * threads waiting for them. - */ - ISchedulingRule[] contestedLocksForThread(Thread owner) { - int threadIndex = indexOf(owner); - ArrayList ownedLocks = new ArrayList(1); - - for (int j = 0; j < graph[threadIndex].length; j++) { - if ((graph[threadIndex][j] > NO_STATE) && (locks.get(j) instanceof ILock)) - // && (isWaitedFor((ISchedulingRule)locks.get(j)))) - ownedLocks.add(locks.get(j)); - } - Assert.isLegal(ownedLocks.size() > 0, "A thread with no contested locks caused deadlock."); //$NON-NLS-1$ - return (ISchedulingRule[]) ownedLocks.toArray(new ISchedulingRule[ownedLocks.size()]); - } - /** - * Reset the deadlock exists variable. - */ - void deadlockSolved() { - deadlockExists = false; - } - /** - * Make sure a thread owns, or is waiting for, at most 1 rule. For debugging - * purposes only. - */ - public void ensureGraphIntegrity(Thread owner, ISchedulingRule rule) { - try { - int threadIndex = lockThreads.indexOf(owner); - if (threadIndex < 0) - return; - for (int j = 0; j < graph[threadIndex].length; j++) { - if ((graph[threadIndex][j] != NO_STATE) && (!(locks.get(j) instanceof ILock)) && (!rule.isConflicting((ISchedulingRule) locks.get(j)))) { - graph[threadIndex][j] = NO_STATE; - removeExtraRows(threadIndex, j); - } - } - } catch (RuntimeException e) { - if (JobManager.DEBUG_LOCKS) { - Policy.debug(false, "Error while ensuring graph integrity: "); //$NON-NLS-1$ - e.printStackTrace(); - } - } - } - /** - * Returns all the locks owned by the given thread - */ - private Object[] getOwnedLocks(Thread current) { - ArrayList ownedLocks = new ArrayList(1); - int index = indexOf(current); - - for (int j = 0; j < graph[index].length; j++) { - if (graph[index][j] > NO_STATE) - ownedLocks.add(locks.get(j)); - } - Assert.isLegal(ownedLocks.size() > 0, "A thread with no locks is part of a deadlock."); //$NON-NLS-1$ - return ownedLocks.toArray(); - } - /** - * Returns the thread that owns the given lock. - */ - private Thread getThreadOwningLock(ISchedulingRule lock) { - int index = indexOf(lock); - - for (int i = 0; i < graph.length; i++) { - if (graph[i][index] > NO_STATE) - return (Thread) lockThreads.get(i); - } - //toDebugString(); - throw new IllegalStateException("Lock " + lock + " is involved in deadlock but is not owned by any thread."); //$NON-NLS-1$ //$NON-NLS-2$ - } - /** - * Returns the true index of the lock in the array. Uses the - * ISchedulingRule.isConflicting relationship. - */ - private int getTrueLockIndex(ISchedulingRule lock) { - for (int i = 0; i < locks.size(); i++) { - ISchedulingRule present = (ISchedulingRule) locks.get(i); - if (present.isConflicting(lock)) - return i; - } - return -1; - } - /** - * Returns the lock the given thread is waiting for. - */ - private Object getWaitingLock(Thread current) { - int index = indexOf(current); - - for (int j = 0; j < graph[index].length; j++) { - if (graph[index][j] == WAITING_FOR_LOCK) - return locks.get(j); - } - throw new IllegalStateException("Thread " + current.getName() + " is involved in deadlock but is not waiting for any lock."); //$NON-NLS-1$ //$NON-NLS-2$ - } - /** - * Returns the index of the given lock in the lock array. If the lock is - * not present in the array, it is added to the end. - */ - private int indexOf(ISchedulingRule lock) { - int index = getTrueLockIndex(lock); - if (index < 0) { - locks.add(lock); - resize = true; - index = locks.size() - 1; - } - return index; - } - /** - * Returns the index of the given thread in the thread array. If the thread - * is not present in the array, it is added to the end. - */ - private int indexOf(Thread owner) { - int index = lockThreads.indexOf(owner); - if (index < 0) { - lockThreads.add(owner); - resize = true; - index = lockThreads.size() - 1; - } - return index; - } - /** - * Returns true IFF deadlock exists - */ - boolean isDeadlocked() { - return deadlockExists; - } - /** - * Returns true if the adjacency matrix is empty. - */ - boolean isEmpty() { - return (locks.size() == 0) && (lockThreads.size() == 0) && (graph.length == 0); - } - /** - * The given lock was aquired by the given thread. - */ - void lockAcquired(Thread owner, ISchedulingRule lock) { - if (!(lock instanceof ILock)) - ensureGraphIntegrity(owner, lock); - int lockIndex = indexOf(lock); - int threadIndex = indexOf(owner); - if (resize) - resizeGraph(); - - //the rule this thread is acquiring may not be the rule - //it was waiting for, so replace the entry in the graph - //also, transfer all entries for any rule that conflicts with this rule - //to the new rule and erase the columns that correspond to the old rules from the graph - if (!(lock instanceof ILock)) { - locks.set(lockIndex, lock); - transferConflictingRules(lock, lockIndex); - } - if (graph[threadIndex][lockIndex] == WAITING_FOR_LOCK) - graph[threadIndex][lockIndex] = 1; - else - graph[threadIndex][lockIndex]++; - } - /** - * The given lock was released by the given thread. Update the graph. - */ - void lockReleased(Thread owner, ISchedulingRule lock) { - if (!lockThreads.contains(owner)) { - if (JobManager.DEBUG_LOCKS) - System.out.println("[lockReleased] Lock " + lock + " was already released by thread " + owner.getName()); //$NON-NLS-1$ //$NON-NLS-2$ - return; - } - if (!locks.contains(lock)) { - if (JobManager.DEBUG_LOCKS) - System.out.println("[lockReleased] Thread " + owner.getName() + " already released lock " + lock); //$NON-NLS-1$ //$NON-NLS-2$ - return; - } - int lockIndex = indexOf(lock); - int threadIndex = indexOf(owner); - - //if this lock was suspended, set it to NO_STATE - if (graph[threadIndex][lockIndex] == WAITING_FOR_LOCK) { - graph[threadIndex][lockIndex] = NO_STATE; - return; - } - if (!JobManager.DEBUG_LOCKS && graph[threadIndex][lockIndex] == NO_STATE) - return; - graph[threadIndex][lockIndex]--; - Assert.isTrue(graph[threadIndex][lockIndex] > -1, "More releases than acquires for thread " + owner.getName() + " and lock " + lock); //$NON-NLS-1$ //$NON-NLS-2$ - if (graph[threadIndex][lockIndex] == NO_STATE) - removeExtraRows(threadIndex, lockIndex); - } - /** - * The given scheduling rule is no longer used because the job that invoked - * it is done Release this rule regardless of how many times it was - * acquired. - */ - void lockReleasedCompletely(Thread owner, ISchedulingRule rule) { - //need to make sure that the given thread was not already removed from the graph - //and that the given rule (not a rule that conflicts with it) was not removed either - if (!lockThreads.contains(owner)) { - if (JobManager.DEBUG_LOCKS) - System.out.println("[lockReleasedCompletely] Lock " + rule + " was already released by thread " + owner.getName()); //$NON-NLS-1$ //$NON-NLS-2$ - return; - } - if (!locks.contains(rule)) { - if (JobManager.DEBUG_LOCKS) - System.out.println("[lockReleasedCompletely] Thread " + owner.getName() + " already released lock " + rule); //$NON-NLS-1$ //$NON-NLS-2$ - return; - } - - int lockIndex = indexOf(rule); - int threadIndex = indexOf(owner); - //set this lock to NO_STATE - graph[threadIndex][lockIndex] = NO_STATE; - removeExtraRows(threadIndex, lockIndex); - } - /** - * The given thread could not get the given lock and is waiting for it. - * Update the graph. - */ - void lockWaitStart(Thread client, ISchedulingRule lock) { - if (!(lock instanceof ILock)) - ensureGraphIntegrity(client, lock); - setToWait(client, lock); - int lockIndex = indexOf(lock); - int[] temp = new int[lockThreads.size()]; - Arrays.fill(temp, 0); - checkWaitCycles(temp, lockIndex); - temp = null; - } - /** - * The given thread has stopped waiting for the given lock. Update the - * graph. - */ - void lockWaitStop(Thread owner, ISchedulingRule lock) { - int lockIndex = getTrueLockIndex(lock); - int threadIndex = lockThreads.indexOf(owner); - if (threadIndex < 0) { - if (JobManager.DEBUG_LOCKS) - System.out.println("Thread " + owner.getName() + " was already removed."); //$NON-NLS-1$ //$NON-NLS-2$ - return; - } - if (lockIndex < 0) { - if (JobManager.DEBUG_LOCKS) - System.out.println("Lock " + lock + " was already removed."); //$NON-NLS-1$ //$NON-NLS-2$ - return; - } - if (graph[threadIndex][lockIndex] != WAITING_FOR_LOCK) - Assert.isTrue(false, "Thread " + owner.getName() + " was not waiting for lock " + lock.toString() + " so it could not time out."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - graph[threadIndex][lockIndex] = NO_STATE; - removeExtraRows(threadIndex, lockIndex); - } - private boolean ownsLocks(Thread owner) { - int index = indexOf(owner); - - for (int j = 0; j < graph[index].length; j++) { - if (graph[index][j] > NO_STATE) { - Object lock = locks.get(j); - if (lock instanceof ILock) - return true; - } - } - return false; - } - /** - * Return true IFF this thread owns rule locks (ie. implicit locks which - * cannot be suspended) - */ - private boolean ownsRuleLocks(Thread owner) { - int index = indexOf(owner); - - for (int j = 0; j < graph[index].length; j++) { - if (graph[index][j] > NO_STATE) { - Object lock = locks.get(j); - if (!(lock instanceof ILock)) - return true; - } - } - return false; - } - /** - * The matrix has been simplified. Check if any unnecessary rows or columns - * can be removed. - */ - private void removeExtraRows(int row, int column) { - boolean rowEmpty = true; - boolean colEmpty = true; - for (int j = 0; j < graph[row].length; j++) { - if (graph[row][j] != NO_STATE) { - rowEmpty = false; - break; - } - } - for (int i = 0; i < graph.length; i++) { - if (graph[i][column] != NO_STATE) { - colEmpty = false; - break; - } - } - if ((!colEmpty) && (!rowEmpty)) - return; - - if (rowEmpty) - lockThreads.remove(row); - else - row = lockThreads.size(); - if (colEmpty) - locks.remove(column); - else - column = locks.size(); - - int[][] temp = new int[lockThreads.size()][locks.size()]; - for (int i = 0; i < row; i++) { - for (int j = 0; j < column; j++) { - temp[i][j] = graph[i][j]; - } - } - - for (int i = row; i < temp.length; i++) { - for (int j = 0; j < temp[i].length; j++) { - temp[i][j] = graph[i + 1][j]; - } - } - - for (int i = 0; i < temp.length; i++) { - for (int j = column; j < temp[i].length; j++) { - temp[i][j] = graph[i][j + 1]; - } - } - - for (int i = row; i < temp.length; i++) { - for (int j = column; j < temp[i].length; j++) { - temp[i][j] = graph[i + 1][j + 1]; - } - } - graph = null; - graph = temp; - } - /** - * Adds a 'deadlock detected' message to the log with a stack trace. - */ - void reportDeadlock(Thread thread, ISchedulingRule lock, Thread toSuspend) { - ArrayList deadlockedThreads = new ArrayList(2); - deadlockedThreads.add(thread); - Thread owner = getThreadOwningLock(lock); - while (!deadlockedThreads.contains(owner)) { - deadlockedThreads.add(owner); - ISchedulingRule waitingLock = (ISchedulingRule) getWaitingLock(owner); - owner = getThreadOwningLock(waitingLock); - } - String msg = "Deadlock detected. All locks owned by thread " + toSuspend.getName() + " will be suspended."; //$NON-NLS-1$ //$NON-NLS-2$ - MultiStatus main = new MultiStatus(IPlatform.PI_RUNTIME, IPlatform.PLUGIN_ERROR, msg, new IllegalStateException()); - for (int i = 0; i < deadlockedThreads.size(); i++) { - Thread current = (Thread) deadlockedThreads.get(i); - Object[] ownedLocks = getOwnedLocks(current); - Object waitLock = getWaitingLock(current); - StringBuffer buf = new StringBuffer("Thread "); //$NON-NLS-1$ - buf.append(current.getName()); - buf.append(" has locks: "); //$NON-NLS-1$ - for (int j = 0; j < ownedLocks.length; j++) { - buf.append(ownedLocks[j]); - buf.append((j < ownedLocks.length - 1) ? ", " : " "); //$NON-NLS-1$ //$NON-NLS-2$ - } - buf.append("and is waiting for lock "); //$NON-NLS-1$ - buf.append(waitLock); - Status child = new Status(IStatus.ERROR, IPlatform.PI_RUNTIME, IPlatform.PLUGIN_ERROR, buf.toString(), null); - main.add(child); - } - InternalPlatform.getDefault().log(main); - } - /** - * The number of threads/locks in the graph has changed. Update the - * underlying matrix. - */ - private void resizeGraph() { - int[][] tempGraph = new int[lockThreads.size()][locks.size()]; - for (int i = 0; i < graph.length; i++) { - for (int j = 0; j < graph[i].length; j++) { - tempGraph[i][j] = graph[i][j]; - } - } - for (int i = 0; i < tempGraph.length; i++) { - for (int j = (graph.length == 0 ? 0 : graph[0].length); j < tempGraph[i].length; j++) { - tempGraph[i][j] = NO_STATE; - } - } - for (int i = graph.length; i < tempGraph.length; i++) { - for (int j = 0; j < tempGraph[i].length; j++) { - tempGraph[i][j] = NO_STATE; - } - } - graph = null; - graph = tempGraph; - resize = false; - } - /** - * Get the thread whose locks can be suspended. (ie. all locks it owns are - * actual locks and not rules) If not found, return the given thread. - */ - Thread resolutionCandidate(Thread thread, ISchedulingRule lock) { - Thread candidate = thread; - //first look for a candidate that has no scheduling rules - for (int i = 0; i < lockThreads.size(); i++) { - if (!ownsRuleLocks(candidate)) - return candidate; - candidate = blockingThread(candidate); - } - //next look for any candidate with a lock - candidate = thread; - for (int i = 0; i < lockThreads.size(); i++) { - if (ownsLocks(candidate)) - return candidate; - candidate = blockingThread(candidate); - } - return thread; - } - /** - * The given thread is waiting for the given lock. Update the graph. - */ - void setToWait(Thread owner, ISchedulingRule lock) { - int lockIndex = indexOf(lock); - int threadIndex = indexOf(owner); - if (resize) - resizeGraph(); - - graph[threadIndex][lockIndex] = WAITING_FOR_LOCK; - } - /** - * Prints out the current matrix to standard output. Only used for - * debugging. - */ - public void toDebugString() { - System.out.println(" :: "); //$NON-NLS-1$ - for (int j = 0; j < locks.size(); j++) { - System.out.print(" " + locks.get(j) + ','); //$NON-NLS-1$ - } - System.out.println(); - for (int i = 0; i < graph.length; i++) { - System.out.print(" " + ((Thread) lockThreads.get(i)).getName() + " : "); //$NON-NLS-1$ //$NON-NLS-2$ - for (int j = 0; j < graph[i].length; j++) { - System.out.print(" " + graph[i][j] + ','); //$NON-NLS-1$ - } - System.out.println(); - } - System.out.println("-------"); //$NON-NLS-1$ - } - /** - * Remove a rule that is conflicting with another rule in the graph - * (conflicting rules can only have 1 entry in the graph) - */ - private void transferConflictingRules(ISchedulingRule rule, int column) { - for (int i = 0; i < locks.size(); i++) { - if ((i != column) && (rule.isConflicting((ISchedulingRule) locks.get(i)))) { - combineColumns(i, column); - } - } - } +/******************************************************************************* + * Copyright (c) 2003 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.jobs; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.eclipse.core.internal.runtime.*; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ILock; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * Stores all the relationships between locks (rules are also considered locks), + * and the threads that own them. All the relationships are stored in a 2D integer array. + * The rows in the array are threads, while the columns are locks. + * Two corresponding arrayLists store the actual threads and locks. + * The index of a thread in the first arrayList is the index of the row in the graph. + * The index of a lock in the second arrayList is the index of the column in the graph. + * An entry greater than 0 in the graph is the number of times a thread in the entry's row + * acquired the lock in the entry's column. + * An entry of -1 means that the thread is waiting to acquire the lock. + * An enry of 0 means that the thread and the lock have no relationship. + * + * The difference between rules and locks is that locks can be suspended, while + * rules are implicit locks and as such cannot be suspended. + * To resolve deadlock, the graph will first try to find a thread that only owns + * locks. Failing that, it will find a thread in the deadlock that owns at least + * one lock and suspend it. + * + * Deadlock can only occur among locks, or among locks in combination with rules. + * Deadlock among rules only is impossible. Therefore, in any deadlock one can always + * find a thread that owns at least one lock that can be suspended. + * + * The implementation of the graph assumes that a thread can only own 1 rule at + * any one time. It can acquire that rule several times, but a thread cannot + * acquire 2 non-conflicting rules at the same time. + * + * The implementation of the graph will sometimes also find and resolve bogus deadlocks. + * graph: assuming this rule hierarchy: + * R2 R3 L1 R1 + * J1 1 0 0 / \ + * J2 0 1 -1 R2 R3 + * J3 -1 0 1 + * + * If in the above situation job4 decides to acquire rule1, then the graph will transform + * to the following: + * R2 R3 R1 L1 + * J1 1 0 1 0 + * J2 1 1 1 -1 + * J3 -1 0 0 1 + * J4 0 0 -1 0 + * + * and the graph will assume that job2 and job3 are deadlocked and suspend lock1 of job3. + * The reason the deadlock is bogus is that the deadlock is unlikely to actually happen (the threads + * are currently not deadlocked, but might deadlock later on when it is too late to detect it) + * Therefore, in order to make sure that no deadlock is possible, + * the deadlock will still be resolved at this point. + */ +class DeadlockDetector { + private static int NO_STATE = 0; + //state variables in the graph + private static int WAITING_FOR_LOCK = -1; + //matrix of relationships between threads and locks + private int[][] graph = new int[0][0]; + //index is column in adjacency matrix for the lock + private final ArrayList locks = new ArrayList(); + //index is row in adjacency matrix for the thread + private final ArrayList lockThreads = new ArrayList(); + //whether the graph needs to be resized + private boolean resize = false; + + /** + * Recursively check if any of the threads that prevent the current thread from running + * are actually deadlocked with the current thread. + * Add the threads that form deadlock to the deadlockedThreads list. + */ + private boolean addCycleThreads(ArrayList deadlockedThreads, Thread next) { + //get the thread that block the given thread from running + Thread[] blocking = blockingThreads(next); + //if the thread is not blocked by other threads, then it is not part of a deadlock + if (blocking.length == 0) + return false; + boolean inCycle = false; + for (int i = 0; i < blocking.length; i++) { + //if we have already visited the given thread, then we found a cycle + if (deadlockedThreads.contains(blocking[i])) { + inCycle = true; + } else { + //otherwise, add the thread to our list and recurse deeper + deadlockedThreads.add(blocking[i]); + //if the thread is not part of a cycle, remove it from the list + if (addCycleThreads(deadlockedThreads, blocking[i])) + inCycle = true; + else + deadlockedThreads.remove(blocking[i]); + } + } + return inCycle; + } + /** + * Get the thread(s) that own the lock this thread is waiting for. + */ + private Thread[] blockingThreads(Thread current) { + //find the lock this thread is waiting for + ISchedulingRule lock = (ISchedulingRule) getWaitingLock(current); + return getThreadsOwningLock(lock); + } + /** + * Check that the addition of a waiting thread did not produce deadlock. + * If deadlock is detected return true, else return false. + */ + private boolean checkWaitCycles(int[] waitingThreads, int lockIndex) { + /** + * find the lock that this thread is waiting for + * recursively check if this is a cylce (ie. a thread waiting on itself) + */ + for (int i = 0; i < graph.length; i++) { + if (graph[i][lockIndex] > NO_STATE) { + if (waitingThreads[i] > NO_STATE) { + return true; + } + //keep track that we already visited this thread + waitingThreads[i]++; + for (int j = 0; j < graph[i].length; j++) { + if (graph[i][j] == WAITING_FOR_LOCK) { + if (checkWaitCycles(waitingThreads, j)) + return true; + } + } + //this thread is not involved in a cycle yet, so remove the visited flag + waitingThreads[i]--; + } + } + return false; + } + /** + * Returns true IFF the matrix contains a row for the given thread. + * (meaning the given thread either owns locks or is waiting for locks) + */ + boolean contains(Thread t) { + return lockThreads.contains(t); + } + /** + * A new rule was just added to the graph. + * Find a rule it conflicts with and update the new rule with the number of times + * it was acquired implicitly when threads acquired conflicting rule. + */ + private void fillPresentEntries(ISchedulingRule newLock, int lockIndex) { + //fill in the entries for the new rule from rules it conflicts with + for (int j = 0; j < locks.size(); j++) { + if ((j != lockIndex) && (newLock.isConflicting((ISchedulingRule) locks.get(j)))) { + for (int i = 0; i < graph.length; i++) { + if ((graph[i][j] > NO_STATE) && (graph[i][lockIndex] == NO_STATE)) + graph[i][lockIndex] = graph[i][j]; + } + } + } + //now back fill the entries for rules the current rule conflicts with + for (int j = 0; j < locks.size(); j++) { + if ((j != lockIndex) && (newLock.isConflicting((ISchedulingRule) locks.get(j)))) { + for (int i = 0; i < graph.length; i++) { + if ((graph[i][lockIndex] > NO_STATE) && (graph[i][j] == NO_STATE)) + graph[i][j] = graph[i][lockIndex]; + } + } + } + } + /** + * Returns all the locks owned by the given thread + */ + private Object[] getOwnedLocks(Thread current) { + ArrayList ownedLocks = new ArrayList(1); + int index = indexOf(current, false); + + for (int j = 0; j < graph[index].length; j++) { + if (graph[index][j] > NO_STATE) + ownedLocks.add(locks.get(j)); + } + if (ownedLocks.size() == 0) + Assert.isLegal(false, "A thread with no locks is part of a deadlock."); //$NON-NLS-1$ + return ownedLocks.toArray(); + } + /** + * Returns an array of threads that form the deadlock (usually 2). + */ + private Thread[] getThreadsInDeadlock(Thread cause) { + ArrayList deadlockedThreads = new ArrayList(2); + /** + * if the thread that caused deadlock doesn't own any locks, then it is not part + * of the deadlock (it just caused it because of a rule it tried to acquire) + */ + if (ownsLocks(cause)) + deadlockedThreads.add(cause); + addCycleThreads(deadlockedThreads, cause); + return (Thread[]) deadlockedThreads.toArray(new Thread[deadlockedThreads.size()]); + } + /** + * Returns the thread(s) that own the given lock. + */ + private Thread[] getThreadsOwningLock(ISchedulingRule rule) { + if (rule == null) + return new Thread[0]; + int lockIndex = indexOf(rule, false); + ArrayList blocking = new ArrayList(1); + for (int i = 0; i < graph.length; i++) { + if (graph[i][lockIndex] > NO_STATE) + blocking.add(lockThreads.get(i)); + } + if ((blocking.size() == 0) && (JobManager.DEBUG_LOCKS)) + System.out.println("Lock " + rule + " is involved in deadlock but is not owned by any thread."); //$NON-NLS-1$ //$NON-NLS-2$ + if ((blocking.size() > 1) && (rule instanceof ILock) && (JobManager.DEBUG_LOCKS)) + System.out.println("Lock " + rule + " is owned by more than 1 thread, but it is not a rule."); //$NON-NLS-1$ //$NON-NLS-2$ + return (Thread[]) blocking.toArray(new Thread[blocking.size()]); + } + /** + * Returns the lock the given thread is waiting for. + */ + private Object getWaitingLock(Thread current) { + int index = indexOf(current, false); + //find the lock that this thread is waiting for + for (int j = 0; j < graph[index].length; j++) { + if (graph[index][j] == WAITING_FOR_LOCK) + return locks.get(j); + } + //it can happen that a thread is not waiting for any lock (it is not really part of the deadlock) + return null; + } + /** + * Returns the index of the given lock in the lock array. If the lock is + * not present in the array, it is added to the end. + */ + private int indexOf(ISchedulingRule lock, boolean add) { + int index = locks.indexOf(lock); + if ((index < 0) && add) { + locks.add(lock); + resize = true; + index = locks.size() - 1; + } + return index; + } + /** + * Returns the index of the given thread in the thread array. If the thread + * is not present in the array, it is added to the end. + */ + private int indexOf(Thread owner, boolean add) { + int index = lockThreads.indexOf(owner); + if ((index < 0) && add) { + lockThreads.add(owner); + resize = true; + index = lockThreads.size() - 1; + } + return index; + } + /** + * Returns true IFF the adjacency matrix is empty. + */ + boolean isEmpty() { + return (locks.size() == 0) && (lockThreads.size() == 0) && (graph.length == 0); + } + /** + * The given lock was aquired by the given thread. + */ + void lockAcquired(Thread owner, ISchedulingRule lock) { + int lockIndex = indexOf(lock, true); + int threadIndex = indexOf(owner, true); + if (resize) + resizeGraph(); + if (graph[threadIndex][lockIndex] == WAITING_FOR_LOCK) + graph[threadIndex][lockIndex] = NO_STATE; + /** + * acquire all locks that conflict with the given lock + * or conflict with a lock the given lock will acquire implicitly + * (locks are acquired implicitly when a conflicting lock is acquired) + */ + ArrayList conflicting = new ArrayList(1); + //only need two passes through all the locks to pick up all conflicting rules + int NUM_PASSES = 2; + conflicting.add(lock); + graph[threadIndex][lockIndex]++; + for (int i = 0; i < NUM_PASSES; i++) { + for (int k = 0; k < conflicting.size(); k++) { + ISchedulingRule current = (ISchedulingRule) conflicting.get(k); + for (int j = 0; j < locks.size(); j++) { + ISchedulingRule possible = (ISchedulingRule) locks.get(j); + if (current.isConflicting(possible) && !conflicting.contains(possible)) { + conflicting.add(possible); + graph[threadIndex][j]++; + } + } + } + } + } + /** + * The given lock was released by the given thread. Update the graph. + */ + void lockReleased(Thread owner, ISchedulingRule lock) { + int lockIndex = indexOf(lock, false); + int threadIndex = indexOf(owner, false); + //make sure the lock and thread exist in the graph + if (threadIndex < 0) { + if (JobManager.DEBUG_LOCKS) + System.out.println("[lockReleased] Lock " + lock + " was already released by thread " + owner.getName()); //$NON-NLS-1$ //$NON-NLS-2$ + return; + } + if (lockIndex < 0) { + if (JobManager.DEBUG_LOCKS) + System.out.println("[lockReleased] Thread " + owner.getName() + " already released lock " + lock); //$NON-NLS-1$ //$NON-NLS-2$ + return; + } + //if this lock was suspended, set it to NO_STATE + if ((lock instanceof ILock) && (graph[threadIndex][lockIndex] == WAITING_FOR_LOCK)) { + graph[threadIndex][lockIndex] = NO_STATE; + return; + } + //release all locks that conflict with the given lock + //or release all rules that are owned by the given thread, if we are releasing a rule + for (int j = 0; j < graph[threadIndex].length; j++) { + if ((lock.isConflicting((ISchedulingRule) locks.get(j))) || (!(lock instanceof ILock) && !(locks.get(j) instanceof ILock) && (graph[threadIndex][j] > NO_STATE))) { + if (graph[threadIndex][j] == NO_STATE) { + if (JobManager.DEBUG_LOCKS) + System.out.println("[lockReleased] More releases than acquires for thread " + owner.getName() + " and lock " + lock); //$NON-NLS-1$ //$NON-NLS-2$ + } else { + graph[threadIndex][j]--; + } + } + } + //if this thread just released the given lock, try to simplify the graph + if (graph[threadIndex][lockIndex] == NO_STATE) + reduceGraph(threadIndex, lock); + } + /** + * The given scheduling rule is no longer used because the job that invoked it is done. + * Release this rule regardless of how many times it was acquired. + */ + void lockReleasedCompletely(Thread owner, ISchedulingRule rule) { + int ruleIndex = indexOf(rule, false); + int threadIndex = indexOf(owner, false); + //need to make sure that the given thread and rule were not already removed from the graph + if (threadIndex < 0) { + if (JobManager.DEBUG_LOCKS) + System.out.println("[lockReleasedCompletely] Lock " + rule + " was already released by thread " + owner.getName()); //$NON-NLS-1$ //$NON-NLS-2$ + return; + } + if (ruleIndex < 0) { + if (JobManager.DEBUG_LOCKS) + System.out.println("[lockReleasedCompletely] Thread " + owner.getName() + " already released lock " + rule); //$NON-NLS-1$ //$NON-NLS-2$ + return; + } + /** + * set all rules that are owned by the given thread to NO_STATE + * (not just rules that conflict with the rule we are releasing) + * if we are releasing a lock, then only update the one entry for the lock + */ + for (int j = 0; j < graph[threadIndex].length; j++) { + if (!(locks.get(j) instanceof ILock) && (graph[threadIndex][j] > NO_STATE)) + graph[threadIndex][j] = NO_STATE; + } + reduceGraph(threadIndex, rule); + } + /** + * The given thread could not get the given lock and is waiting for it. + * Update the graph. + */ + Deadlock lockWaitStart(Thread client, ISchedulingRule lock) { + setToWait(client, lock, false); + int lockIndex = indexOf(lock, false); + int[] temp = new int[lockThreads.size()]; + //check if the addition of the waiting thread caused deadlock + if (!checkWaitCycles(temp, lockIndex)) + return null; + //there is a deadlock in the graph + Thread[] threads = getThreadsInDeadlock(client); + Thread candidate = resolutionCandidate(threads); + ISchedulingRule[] locks = realLocksForThread(candidate); + Deadlock deadlock = new Deadlock(threads, locks, candidate); + //find a thread whose locks can be suspended to resolve the deadlock + if (JobManager.DEBUG_LOCKS) + reportDeadlock(deadlock); + if (JobManager.DEBUG_DEADLOCK) + throw new IllegalStateException("Deadlock detected. Caused by thread " + client.getName() + '.'); //$NON-NLS-1$ + //update the graph to indicate that the locks will now be suspended + // to indicate that the lock will be suspended, we set the thread to wait for the lock + // when the lock is forced to be released, the entry will be cleared + for (int i = 0; i < locks.length; i++) + setToWait(deadlock.getCandidate(), locks[i], true); + return deadlock; + } + /** + * The given thread has stopped waiting for the given lock. + * Update the graph. + */ + void lockWaitStop(Thread owner, ISchedulingRule lock) { + int lockIndex = indexOf(lock, false); + int threadIndex = indexOf(owner, false); + //make sure the thread and lock exist in the graph + if (threadIndex < 0) { + if (JobManager.DEBUG_LOCKS) + System.out.println("Thread " + owner.getName() + " was already removed."); //$NON-NLS-1$ //$NON-NLS-2$ + return; + } + if (lockIndex < 0) { + if (JobManager.DEBUG_LOCKS) + System.out.println("Lock " + lock + " was already removed."); //$NON-NLS-1$ //$NON-NLS-2$ + return; + } + if (graph[threadIndex][lockIndex] != WAITING_FOR_LOCK) + Assert.isTrue(false, "Thread " + owner.getName() + " was not waiting for lock " + lock.toString() + " so it could not time out."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + graph[threadIndex][lockIndex] = NO_STATE; + reduceGraph(threadIndex, lock); + } + /** + * Returns true IFF the given thread owns a single lock + */ + private boolean ownsLocks(Thread cause) { + int threadIndex = indexOf(cause, false); + for (int j = 0; j < graph[threadIndex].length; j++) { + if (graph[threadIndex][j] > NO_STATE) + return true; + } + return false; + } + /** + * Returns true IFF the given thread owns a single real lock. + * A real lock is a lock that can be suspended. + */ + private boolean ownsRealLocks(Thread owner) { + int threadIndex = indexOf(owner, false); + for (int j = 0; j < graph[threadIndex].length; j++) { + if (graph[threadIndex][j] > NO_STATE) { + Object lock = locks.get(j); + if (lock instanceof ILock) + return true; + } + } + return false; + } + /** + * Return true IFF this thread owns rule locks (ie. implicit locks which + * cannot be suspended) + */ + private boolean ownsRuleLocks(Thread owner) { + int threadIndex = indexOf(owner, false); + for (int j = 0; j < graph[threadIndex].length; j++) { + if (graph[threadIndex][j] > NO_STATE) { + Object lock = locks.get(j); + if (!(lock instanceof ILock)) + return true; + } + } + return false; + } + /** + * Returns an array of real locks that are owned by the given thread. + * Real locks are locks that implement the ILock interface and can be suspended. + */ + private ISchedulingRule[] realLocksForThread(Thread owner) { + int threadIndex = indexOf(owner, false); + ArrayList ownedLocks = new ArrayList(1); + for (int j = 0; j < graph[threadIndex].length; j++) { + if ((graph[threadIndex][j] > NO_STATE) && (locks.get(j) instanceof ILock)) + ownedLocks.add(locks.get(j)); + } + if (ownedLocks.size() == 0) + Assert.isLegal(false, "A thread with no real locks was chosen to resolve deadlock."); //$NON-NLS-1$ + return (ISchedulingRule[]) ownedLocks.toArray(new ISchedulingRule[ownedLocks.size()]); + } + /** + * The matrix has been simplified. Check if any unnecessary rows or columns + * can be removed. + */ + private void reduceGraph(int row, ISchedulingRule lock) { + boolean[] emptyColumns = new boolean[locks.size()]; + Arrays.fill(emptyColumns, false); + + /** + * find all columns that could possibly be empty + * (consist of locks which conflict with the given lock, or of locks which are rules) + */ + for (int j = 0; j < locks.size(); j++) { + if ((lock.isConflicting((ISchedulingRule) locks.get(j))) || !(locks.get(j) instanceof ILock)) + emptyColumns[j] = true; + } + + boolean rowEmpty = true; + int numEmpty = 0; + //check is the given row is empty + for (int j = 0; j < graph[row].length; j++) { + if (graph[row][j] != NO_STATE) { + rowEmpty = false; + break; + } + } + /** + * Check if the possibly empty columns are actually empty. + * If a column is actually empty, remove the corresponding lock from the list of locks + * Start at the last column so that when locks are removed from the list, + * the index of the remaining locks is unchanged. Store the number of empty columns. + */ + for (int j = emptyColumns.length - 1; j >= 0; j--) { + for (int i = 0; i < graph.length; i++) { + if (emptyColumns[j] && (graph[i][j] != NO_STATE)) { + emptyColumns[j] = false; + break; + } + } + if (emptyColumns[j]) { + locks.remove(j); + numEmpty++; + } + } + //if no columns or rows are empty, return right away + if ((numEmpty == 0) && (!rowEmpty)) + return; + + if (rowEmpty) + lockThreads.remove(row); + + //new graph (the list of locks and the list of threads are already updated) + int[][] temp = new int[lockThreads.size()][locks.size()]; + + //the number of rows we need to skip to get the correct entry from the old graph + int numRowsSkipped = 0; + for (int i = 0; i < graph.length - numRowsSkipped; i++) { + if ((i == row) && rowEmpty) { + numRowsSkipped++; + //check if we need to skip the last row + if (i >= graph.length - numRowsSkipped) + break; + } + //the nuber of columns we need to skip to get the correct entry from the old graph + //needs to be reset for every new row + int numColsSkipped = 0; + for (int j = 0; j < graph[i].length - numColsSkipped; j++) { + while (emptyColumns[j + numColsSkipped]) { + numColsSkipped++; + //check if we need to skip the last column + if (j >= graph[i].length - numColsSkipped) + break; + } + //need to break out of the outer loop + if (j >= graph[i].length - numColsSkipped) + break; + temp[i][j] = graph[i + numRowsSkipped][j + numColsSkipped]; + } + } + graph = null; + graph = temp; + Assert.isTrue(lockThreads.size() == graph.length, "Rows and threads don't match."); //$NON-NLS-1$ + Assert.isTrue(locks.size() == ((graph.length > 0) ? graph[0].length : 0), "Columns and locks don't match."); //$NON-NLS-1$ + } + /** + * Adds a 'deadlock detected' message to the log with a stack trace. + */ + private void reportDeadlock(Deadlock deadlock) { + String msg = "Deadlock detected. All locks owned by thread " + deadlock.getCandidate().getName() + " will be suspended."; //$NON-NLS-1$ //$NON-NLS-2$ + MultiStatus main = new MultiStatus(IPlatform.PI_RUNTIME, IPlatform.PLUGIN_ERROR, msg, new IllegalStateException()); + Thread[] threads = deadlock.getThreads(); + for (int i = 0; i < threads.length; i++) { + Object[] ownedLocks = getOwnedLocks(threads[i]); + Object waitLock = getWaitingLock(threads[i]); + StringBuffer buf = new StringBuffer("Thread "); //$NON-NLS-1$ + buf.append(threads[i].getName()); + buf.append(" has locks: "); //$NON-NLS-1$ + for (int j = 0; j < ownedLocks.length; j++) { + buf.append(ownedLocks[j]); + buf.append((j < ownedLocks.length - 1) ? ", " : " "); //$NON-NLS-1$ //$NON-NLS-2$ + } + buf.append("and is waiting for lock "); //$NON-NLS-1$ + buf.append(waitLock); + Status child = new Status(IStatus.ERROR, IPlatform.PI_RUNTIME, IPlatform.PLUGIN_ERROR, buf.toString(), null); + main.add(child); + } + InternalPlatform.getDefault().log(main); + } + /** + * The number of threads/locks in the graph has changed. Update the + * underlying matrix. + */ + private void resizeGraph() { + // a new row and/or a new column was added to the graph. + // since new rows/columns are always added to the end, just transfer + // old entries to the new graph, with the same indices. + int[][] tempGraph = new int[lockThreads.size()][locks.size()]; + for (int i = 0; i < graph.length; i++) + System.arraycopy(graph[i], 0, tempGraph[i], 0, graph[i].length); + graph = tempGraph; + resize = false; + } + /** + * Get the thread whose locks can be suspended. (ie. all locks it owns are + * actual locks and not rules). Return the first thread in the array by default. + */ + private Thread resolutionCandidate(Thread[] candidates) { + //first look for a candidate that has no scheduling rules + for (int i = 0; i < candidates.length; i++) { + if (!ownsRuleLocks(candidates[i])) + return candidates[i]; + } + //next look for any candidate with a real lock (a lock that can be suspended) + for (int i = 0; i < candidates.length; i++) { + if (ownsRealLocks(candidates[i])) + return candidates[i]; + } + //unnecessary, return the first entry in the array by default + return candidates[0]; + } + /** + * The given thread is waiting for the given lock. Update the graph. + */ + private void setToWait(Thread owner, ISchedulingRule lock, boolean suspend) { + boolean needTransfer = false; + /** + * if we are adding an entry where a thread is waiting on a scheduling rule, + * then we need to transfer all positive entries for a conflicting rule to the + * newly added rule in order to synchronize the graph. + */ + if (!suspend && !(lock instanceof ILock)) + needTransfer = true; + int lockIndex = indexOf(lock, !suspend); + int threadIndex = indexOf(owner, !suspend); + if (resize) + resizeGraph(); + + graph[threadIndex][lockIndex] = WAITING_FOR_LOCK; + if (needTransfer) + fillPresentEntries(lock, lockIndex); + } + /** + * Prints out the current matrix to standard output. + * Only used for debugging. + */ + public void toDebugString() { + System.out.println(" :: "); //$NON-NLS-1$ + for (int j = 0; j < locks.size(); j++) { + System.out.print(" " + locks.get(j) + ','); //$NON-NLS-1$ + } + System.out.println(); + for (int i = 0; i < graph.length; i++) { + System.out.print(" " + ((Thread) lockThreads.get(i)).getName() + " : "); //$NON-NLS-1$ //$NON-NLS-2$ + for (int j = 0; j < graph[i].length; j++) { + System.out.print(" " + graph[i][j] + ','); //$NON-NLS-1$ + } + System.out.println(); + } + System.out.println("-------"); //$NON-NLS-1$ + } } \ No newline at end of file
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/LockManager.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/LockManager.java index e16a09f..a3005a4 100644 --- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/LockManager.java +++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/LockManager.java
@@ -1,221 +1,221 @@ -/********************************************************************** - * Copyright (c) 2003 IBM Corporation and others. All rights reserved. This - * program and the accompanying materials are made available under the terms of - * the Common Public License v1.0 which accompanies this distribution, and is - * available at http://www.eclipse.org/legal/cpl-v10.html - * - * Contributors: - * IBM - Initial API and implementation - **********************************************************************/ -package org.eclipse.core.internal.jobs; - -import java.util.HashMap; -import java.util.Stack; -import org.eclipse.core.internal.runtime.InternalPlatform; -import org.eclipse.core.internal.runtime.Policy; -import org.eclipse.core.runtime.*; -import org.eclipse.core.runtime.jobs.ISchedulingRule; -import org.eclipse.core.runtime.jobs.LockListener; - -public class LockManager { - - /** - * This class captures the state of suspended locks. Locks are suspended if - * deadlock is detected. - */ - private static class LockState { - private int depth; - private OrderedLock lock; - /** - * Suspends ownership of the given lock, and returns the saved state. - */ - protected static LockState suspend(OrderedLock lock) { - LockState state = new LockState(); - state.lock = lock; - state.depth = lock.forceRelease(); - return state; - } - /** - * Re-acquires a suspended lock and reverts to the correct lock depth. - */ - public void resume() { - //spin until the lock is successfully acquired - //NOTE: spinning here allows the UI thread to service pending syncExecs - //if the UI thread is waiting to acquire a lock. - while (true) { - try { - if (lock.acquire(Long.MAX_VALUE)) - break; - } catch (InterruptedException e) { - } - } - lock.setDepth(depth); - } - } - - //the lock listener for this lock manager - protected LockListener lockListener; - /* - * The internal data structure that stores all the relationships - * between the locks and the threads that own them. - */ - private DeadlockDetector locks = new DeadlockDetector(); - /* - * Stores thread - stack pairs where every entry in the stack is an array - * of locks that were suspended while the thread was aquiring more locks - * (a stack is needed because when a thread tries to reaquire suspended locks, - * it can cause deadlock, and some locks it owns can be suspended again) - */ - private HashMap suspendedLocks = new HashMap(); - - public LockManager() { - } - /* (non-Javadoc) - * Method declared on LockListener - */ - public void aboutToRelease() { - if (lockListener == null) - return; - try { - lockListener.aboutToRelease(); - } catch (Exception e) { - handleException(e); - } catch (LinkageError e) { - handleException(e); - } - } - /* (non-Javadoc) - * Method declared on LockListener - */ - public boolean aboutToWait(Thread lockOwner) { - if (lockListener == null) - return false; - try { - return lockListener.aboutToWait(lockOwner); - } catch (Exception e) { - handleException(e); - } catch (LinkageError e) { - handleException(e); - } - return false; - } - /** - * This thread has just acquired a lock. Update graph. - */ - void addLockThread(Thread thread, ISchedulingRule lock) { - synchronized (locks) { - locks.lockAcquired(thread, lock); - } - } - /** - * This thread has just been refused a lock. Update graph and check for deadlock. - */ - void addLockWaitThread(Thread thread, ISchedulingRule lock) { - synchronized (locks) { - locks.lockWaitStart(thread, lock); - if (locks.isDeadlocked()) { - Thread candidate = locks.resolutionCandidate(thread, lock); - if (JobManager.DEBUG_LOCKS) - locks.reportDeadlock(thread, lock, candidate); - if (JobManager.DEBUG_DEADLOCK) - throw new IllegalStateException("Deadlock detected. Caused by thread " + thread.getName() + '.'); //$NON-NLS-1$ - ISchedulingRule[] toSuspend = locks.contestedLocksForThread(candidate); - LockState[] suspended = new LockState[toSuspend.length]; - for (int i = 0; i < toSuspend.length; i++) { - locks.setToWait(candidate, toSuspend[i]); - suspended[i] = LockState.suspend((OrderedLock) toSuspend[i]); - } - synchronized (suspendedLocks) { - Stack prevLocks = (Stack) suspendedLocks.get(candidate); - if (prevLocks == null) - prevLocks = new Stack(); - - prevLocks.push(suspended); - suspendedLocks.put(candidate, prevLocks); - } - locks.deadlockSolved(); - } - } - } - private static void handleException(Throwable e) { - String message = Policy.bind("jobs.internalError"); //$NON-NLS-1$ - IStatus status; - if (e instanceof CoreException) { - status = new MultiStatus(IPlatform.PI_RUNTIME, IPlatform.PLUGIN_ERROR, message, e); - ((MultiStatus) status).merge(((CoreException) e).getStatus()); - } else { - status = new Status(IStatus.ERROR, IPlatform.PI_RUNTIME, IPlatform.PLUGIN_ERROR, message, e); - } - InternalPlatform.getDefault().log(status); - } - /** - * Returns true IFF the underlying graph is empty. - * Used in debugging. - */ - public boolean isEmpty() { - return locks.isEmpty(); - } - /** - * Returns true IFF this thread either owns, or is waiting for, any locks. - */ - public boolean isLockOwner() { - //all job threads have to be treated as lock owners because UI thread - //may try to join a job - Thread current = Thread.currentThread(); - if (current instanceof Worker) - return true; - synchronized (locks) { - return locks.contains(Thread.currentThread()); - } - } - /** - * Creates and returns a new lock. - */ - public synchronized OrderedLock newLock() { - OrderedLock result = new OrderedLock(this); - return result; - } - /** - * Releases all the acquires that were called on the given rule. Needs to be called only once. - */ - void removeLockCompletely(Thread thread, ISchedulingRule rule) { - synchronized (locks) { - locks.lockReleasedCompletely(thread, rule); - } - } - /** - * This thread has just released a lock. Update graph. - */ - void removeLockThread(Thread thread, ISchedulingRule lock) { - synchronized (locks) { - locks.lockReleased(thread, lock); - } - } - /** - * This thread has just stopped waiting for a lock. Update graph. - */ - void removeLockWaitThread(Thread thread, ISchedulingRule lock) { - synchronized (locks) { - locks.lockWaitStop(thread, lock); - } - } - /** - * Returns all the locks that were suspended while this thread was waiting to acquire another lock. - */ - void resumeSuspendedLocks(Thread owner) { - LockState[] toResume; - synchronized (suspendedLocks) { - Stack prevLocks = (Stack) suspendedLocks.get(owner); - if (prevLocks == null) - return; - toResume = (LockState[]) prevLocks.pop(); - if (prevLocks.empty()) - suspendedLocks.remove(owner); - } - for (int i = 0; i < toResume.length; i++) - toResume[i].resume(); - } - public void setLockListener(LockListener listener) { - this.lockListener = listener; - } +/********************************************************************** + * Copyright (c) 2003 IBM Corporation and others. All rights reserved. This + * program and the accompanying materials are made available under the terms of + * the Common Public License v1.0 which accompanies this distribution, and is + * available at http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM - Initial API and implementation + **********************************************************************/ +package org.eclipse.core.internal.jobs; + +import java.util.HashMap; +import java.util.Stack; +import org.eclipse.core.internal.runtime.InternalPlatform; +import org.eclipse.core.internal.runtime.Policy; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.LockListener; + +/** + * Stores the only reference to the graph that contains all the known + * relationships between locks, rules, and the threads that own them. + * Synchronizes all access to the graph on the only instance that exists in this class. + * + * Also stores the state of suspended locks so that they can be reacquired with + * the proper lock depth. + */ +public class LockManager { + + /** + * This class captures the state of suspended locks. + * Locks are suspended if deadlock is detected. + */ + private static class LockState { + private int depth; + private OrderedLock lock; + /** + * Suspends ownership of the given lock, and returns the saved state. + */ + protected static LockState suspend(OrderedLock lock) { + LockState state = new LockState(); + state.lock = lock; + state.depth = lock.forceRelease(); + return state; + } + /** + * Re-acquires a suspended lock and reverts to the correct lock depth. + */ + public void resume() { + //spin until the lock is successfully acquired + //NOTE: spinning here allows the UI thread to service pending syncExecs + //if the UI thread is waiting to acquire a lock. + while (true) { + try { + if (lock.acquire(Long.MAX_VALUE)) + break; + } catch (InterruptedException e) { + } + } + lock.setDepth(depth); + } + } + + //the lock listener for this lock manager + protected LockListener lockListener; + /* + * The internal data structure that stores all the relationships + * between the locks (or rules) and the threads that own them. + */ + private DeadlockDetector locks = new DeadlockDetector(); + /* + * Stores thread - stack pairs where every entry in the stack is an array + * of locks that were suspended while the thread was aquiring more locks + * (a stack is needed because when a thread tries to reaquire suspended locks, + * it can cause deadlock, and some locks it owns can be suspended again) + */ + private HashMap suspendedLocks = new HashMap(); + + public LockManager() { + } + /* (non-Javadoc) + * Method declared on LockListener + */ + public void aboutToRelease() { + if (lockListener == null) + return; + try { + lockListener.aboutToRelease(); + } catch (Exception e) { + handleException(e); + } catch (LinkageError e) { + handleException(e); + } + } + /* (non-Javadoc) + * Method declared on LockListener + */ + public boolean aboutToWait(Thread lockOwner) { + if (lockListener == null) + return false; + try { + return lockListener.aboutToWait(lockOwner); + } catch (Exception e) { + handleException(e); + } catch (LinkageError e) { + handleException(e); + } + return false; + } + /** + * This thread has just acquired a lock. Update graph. + */ + void addLockThread(Thread thread, ISchedulingRule lock) { + synchronized (locks) { + locks.lockAcquired(thread, lock); + } + } + /** + * This thread has just been refused a lock. Update graph and check for deadlock. + */ + void addLockWaitThread(Thread thread, ISchedulingRule lock) { + synchronized (locks) { + Deadlock found = locks.lockWaitStart(thread, lock); + // if deadlock was detected, the found variable will contain all the information about it, + // including which locks to suspend for which thread to resolve the deadlock. + if (found != null) { + ISchedulingRule[] toSuspend = found.getLocks(); + LockState[] suspended = new LockState[toSuspend.length]; + for (int i = 0; i < toSuspend.length; i++) + suspended[i] = LockState.suspend((OrderedLock) toSuspend[i]); + synchronized (suspendedLocks) { + Stack prevLocks = (Stack) suspendedLocks.get(found.getCandidate()); + if (prevLocks == null) + prevLocks = new Stack(); + prevLocks.push(suspended); + suspendedLocks.put(found.getCandidate(), prevLocks); + } + } + } + } + private static void handleException(Throwable e) { + String message = Policy.bind("jobs.internalError"); //$NON-NLS-1$ + IStatus status; + if (e instanceof CoreException) { + status = new MultiStatus(IPlatform.PI_RUNTIME, IPlatform.PLUGIN_ERROR, message, e); + ((MultiStatus) status).merge(((CoreException) e).getStatus()); + } else { + status = new Status(IStatus.ERROR, IPlatform.PI_RUNTIME, IPlatform.PLUGIN_ERROR, message, e); + } + InternalPlatform.getDefault().log(status); + } + /** + * Returns true IFF the underlying graph is empty. + * For debugging purposes only. + */ + public boolean isEmpty() { + return locks.isEmpty(); + } + /** + * Returns true IFF this thread either owns, or is waiting for, any locks or rules. + */ + public boolean isLockOwner() { + //all job threads have to be treated as lock owners because UI thread + //may try to join a job + Thread current = Thread.currentThread(); + if (current instanceof Worker) + return true; + synchronized (locks) { + return locks.contains(Thread.currentThread()); + } + } + /** + * Creates and returns a new lock. + */ + public synchronized OrderedLock newLock() { + return new OrderedLock(this); + } + /** + * Releases all the acquires that were called on the given rule. Needs to be called only once. + */ + void removeLockCompletely(Thread thread, ISchedulingRule rule) { + synchronized (locks) { + locks.lockReleasedCompletely(thread, rule); + } + } + /** + * This thread has just released a lock. Update graph. + */ + void removeLockThread(Thread thread, ISchedulingRule lock) { + synchronized (locks) { + locks.lockReleased(thread, lock); + } + } + /** + * This thread has just stopped waiting for a lock. Update graph. + */ + void removeLockWaitThread(Thread thread, ISchedulingRule lock) { + synchronized (locks) { + locks.lockWaitStop(thread, lock); + } + } + /** + * Resumes all the locks that were suspended while this thread was waiting to acquire another lock. + */ + void resumeSuspendedLocks(Thread owner) { + LockState[] toResume; + synchronized (suspendedLocks) { + Stack prevLocks = (Stack) suspendedLocks.get(owner); + if (prevLocks == null) + return; + toResume = (LockState[]) prevLocks.pop(); + if (prevLocks.empty()) + suspendedLocks.remove(owner); + } + for (int i = 0; i < toResume.length; i++) + toResume[i].resume(); + } + public void setLockListener(LockListener listener) { + this.lockListener = listener; + } } \ No newline at end of file
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/OrderedLock.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/OrderedLock.java index 1f68923..2a03e14 100644 --- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/OrderedLock.java +++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/OrderedLock.java
@@ -1,260 +1,264 @@ -/********************************************************************** - * Copyright (c) 2003 IBM Corporation and others. All rights reserved. This - * program and the accompanying materials are made available under the terms of - * the Common Public License v1.0 which accompanies this distribution, and is - * available at http://www.eclipse.org/legal/cpl-v10.html - * - * Contributors: - * IBM - Initial API and implementation - **********************************************************************/ -package org.eclipse.core.internal.jobs; - -import org.eclipse.core.internal.runtime.Assert; -import org.eclipse.core.runtime.jobs.ILock; -import org.eclipse.core.runtime.jobs.ISchedulingRule; - -/** - * A lock used to control write access to an exclusive resource. - * - * The lock avoids circular waiting deadlocks by ensuring that locks - * are always acquired in a strict order. This makes it impossible for n such - * locks to deadlock while waiting for each other. The downside is that this means - * that during an interval when a process owns a lock, it can be forced - * to give the lock up and wait until all locks it requires become - * available. This removes the feature of exclusive access to the - * resource in contention for the duration between acquire() and - * release() calls. - * - * The lock implementation prevents starvation by granting the - * lock in the same order in which acquire() requests arrive. In - * this scheme, starvation is only possible if a thread retains - * a lock indefinitely. - */ -public class OrderedLock implements ILock, ISchedulingRule { - - private static final boolean DEBUG = false; - /** - * Locks are sequentially ordered for debugging purposes. - */ - private static int nextLockNumber = 0; - /** - * The thread of the operation that currently owns the lock. - */ - private volatile Thread currentOperationThread; - /** - * Records the number of successive acquires in the same - * thread. The lock is released only when the depth - * reaches zero. - */ - private int depth; - /** - * The manager that implements the deadlock detection and resolution protocol. - */ - private final LockManager manager; - private final int number; - /** - * Queue of semaphores for operations currently waiting - * on the lock. - */ - private final Queue operations = new Queue(); - - /** - * Creates a new workspace lock. - */ - OrderedLock(LockManager manager) { - this.manager = manager; - this.number = nextLockNumber++; - } - /* (non-Javadoc) - * @see Locks.ILock#acquire() - */ - public void acquire() { - while (true) { - try { - if (acquire(Long.MAX_VALUE)) - return; - } catch (InterruptedException e) { - } - } - } - /* (non-Javadoc) - * @see Locks.ILock#acquire(long) - */ - public boolean acquire(long delay) throws InterruptedException { - if (Thread.interrupted()) - throw new InterruptedException(); - - boolean success = false; - if (delay <= 0) - return attempt(); - else { - Semaphore semaphore = createSemaphore(); - if (semaphore == null) - return true; - else { - if (DEBUG) - System.out.println("[" + Thread.currentThread() + "] Operation waiting to be executed... " + this); //$NON-NLS-1$ //$NON-NLS-2$ - - success = doAcquire(semaphore, delay); - manager.resumeSuspendedLocks(Thread.currentThread()); - if (DEBUG && success) - System.out.println("[" + Thread.currentThread() + "] Operation started... " + this); //$NON-NLS-1$ //$NON-NLS-2$ - else if (DEBUG) - System.out.println("[" + Thread.currentThread() + "] Operation timed out... " + this); //$NON-NLS-1$ //$NON-NLS-2$ - } - } - return success; - } - /** - * Attempts to acquire the lock. Returns false if the lock is not available and - * true if the lock has been successfully acquired. - */ - private synchronized boolean attempt() { - //return true if we already own the lock - //also, if nobody is waiting, grant the lock immediately - if ((currentOperationThread == Thread.currentThread()) || (currentOperationThread == null && operations.isEmpty())) { - depth++; - setCurrentOperationThread(Thread.currentThread()); - return true; - } - return false; - } - /* (non-Javadoc) - * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) - */ - public boolean contains(ISchedulingRule rule) { - return false; - } - /** - * Returns null if acquired and a Semaphore object otherwise. - */ - private synchronized Semaphore createSemaphore() { - return attempt() ? null : enqueue(new Semaphore(Thread.currentThread())); - } - /** - * Attempts to acquire this lock. Callers will block until this lock comes available to - * them, or until the specified delay has elapsed. - */ - private boolean doAcquire(Semaphore semaphore, long delay) throws InterruptedException { - boolean success = false; - //notify hook to service pending syncExecs before falling asleep - if (manager.aboutToWait(this.currentOperationThread)) { - //hook granted immediate access - //remove semaphore for the lock request from the queue - //do not log in graph because this thread did not really get the lock - operations.remove(semaphore); - depth++; - manager.addLockThread(currentOperationThread, this); - return true; - } - manager.addLockWaitThread(Thread.currentThread(), this); - try { - success = semaphore.acquire(delay); - } catch (InterruptedException e) { - if (DEBUG) - System.out.println("[" + Thread.currentThread() + "] Operation interrupted while waiting... :-|"); //$NON-NLS-1$ //$NON-NLS-2$ - throw e; - } - if (success) { - depth++; - updateCurrentOperation(); - } else { - //operation timed out - //remove request semaphore from queue and update graph - operations.remove(semaphore); - manager.removeLockWaitThread(Thread.currentThread(), this); - } - return success; - } - /** - * Releases this lock from the thread that used to own it. - * Grants this lock to the next thread in the queue. - */ - private synchronized void doRelease() { - //notify hook - manager.aboutToRelease(); - depth = 0; - Semaphore next = (Semaphore) operations.peek(); - setCurrentOperationThread(null); - if (next != null) - next.release(); - } - /** - * If there is another semaphore with the same runnable in the - * queue, the other is returned and the new one is not added. - */ - private synchronized Semaphore enqueue(Semaphore newSemaphore) { - Semaphore semaphore = (Semaphore) operations.get(newSemaphore); - if (semaphore == null) { - operations.enqueue(newSemaphore); - return newSemaphore; - } - return semaphore; - } - /** - * Suspend this lock by granting the lock to the next lock in the queue. - * Return the depth of the suspended lock. - */ - protected int forceRelease() { - int oldDepth = depth; - doRelease(); - return oldDepth; - } - /* (non-Javadoc) - * @see Locks.ILock#getDepth() - */ - public int getDepth() { - return depth; - } - /* (non-Javadoc) - * @see org.eclipse.core.runtime.jobs.ISchedulingRule#isConflicting(org.eclipse.core.runtime.jobs.ISchedulingRule) - */ - public boolean isConflicting(ISchedulingRule rule) { - return rule == this; - } - /* (non-Javadoc) - * @see Locks.ILock#release() - */ - public void release() { - if (depth == 0) - return; - //only release the lock when the depth reaches zero - Assert.isTrue(depth >= 0, "Lock released too many times"); //$NON-NLS-1$ - if (--depth == 0) - doRelease(); - else - manager.removeLockThread(currentOperationThread, this); - } - /** - * If newThread is null, release this lock from its previous owner. - * If newThread is not null, grant this lock to newThread. - */ - private void setCurrentOperationThread(Thread newThread) { - if ((currentOperationThread != null) && (newThread == null)) - manager.removeLockThread(currentOperationThread, this); - this.currentOperationThread = newThread; - if (currentOperationThread != null) - manager.addLockThread(currentOperationThread, this); - } - /** - * Forces the lock to be at the given depth. Used when re-acquiring a suspended - * lock. - */ - protected void setDepth(int newDepth) { - this.depth = newDepth; - } - /** - * For debugging purposes only. - */ - public String toString() { - return "OrderedLock (" + number + ")"; //$NON-NLS-1$ //$NON-NLS-2$ - } - /** - * This lock has just been granted to a new thread (the thread waited for it). - * Remove the request from the queue and update both the graph and the lock. - */ - private synchronized void updateCurrentOperation() { - operations.dequeue(); - setCurrentOperationThread(Thread.currentThread()); - } +/********************************************************************** + * Copyright (c) 2003 IBM Corporation and others. All rights reserved. This + * program and the accompanying materials are made available under the terms of + * the Common Public License v1.0 which accompanies this distribution, and is + * available at http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM - Initial API and implementation + **********************************************************************/ +package org.eclipse.core.internal.jobs; + +import org.eclipse.core.internal.runtime.Assert; +import org.eclipse.core.runtime.jobs.ILock; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * A lock used to control write access to an exclusive resource. + * + * The lock avoids circular waiting deadlocks by detecting the deadlocks + * and resolving them through the suspension of all locks owned by one + * of the threads involved in the deadlock. This makes it impossible for n such + * locks to deadlock while waiting for each other. The downside is that this means + * that during an interval when a process owns a lock, it can be forced + * to give the lock up and wait until all locks it requires become + * available. This removes the feature of exclusive access to the + * resource in contention for the duration between acquire() and + * release() calls. + * + * The lock implementation prevents starvation by granting the + * lock in the same order in which acquire() requests arrive. In + * this scheme, starvation is only possible if a thread retains + * a lock indefinitely. + */ +public class OrderedLock implements ILock, ISchedulingRule { + + private static final boolean DEBUG = false; + /** + * Locks are sequentially ordered for debugging purposes. + */ + private static int nextLockNumber = 0; + /** + * The thread of the operation that currently owns the lock. + */ + private volatile Thread currentOperationThread; + /** + * Records the number of successive acquires in the same + * thread. The lock is released only when the depth + * reaches zero. + */ + private int depth; + /** + * The manager that implements the deadlock detection and resolution protocol. + */ + private final LockManager manager; + private final int number; + /** + * Queue of semaphores for threads currently waiting + * on the lock. + */ + private final Queue operations = new Queue(); + + /** + * Creates a new workspace lock. + */ + OrderedLock(LockManager manager) { + this.manager = manager; + this.number = nextLockNumber++; + } + /* (non-Javadoc) + * @see Locks.ILock#acquire() + */ + public void acquire() { + while (true) { + try { + if (acquire(Long.MAX_VALUE)) + return; + } catch (InterruptedException e) { + } + } + } + /* (non-Javadoc) + * @see Locks.ILock#acquire(long) + */ + public boolean acquire(long delay) throws InterruptedException { + if (Thread.interrupted()) + throw new InterruptedException(); + + boolean success = false; + if (delay <= 0) + return attempt(); + else { + Semaphore semaphore = createSemaphore(); + if (semaphore == null) + return true; + else { + if (DEBUG) + System.out.println("[" + Thread.currentThread() + "] Operation waiting to be executed... " + this); //$NON-NLS-1$ //$NON-NLS-2$ + + success = doAcquire(semaphore, delay); + manager.resumeSuspendedLocks(Thread.currentThread()); + if (DEBUG && success) + System.out.println("[" + Thread.currentThread() + "] Operation started... " + this); //$NON-NLS-1$ //$NON-NLS-2$ + else if (DEBUG) + System.out.println("[" + Thread.currentThread() + "] Operation timed out... " + this); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + return success; + } + /** + * Attempts to acquire the lock. Returns false if the lock is not available and + * true if the lock has been successfully acquired. + */ + private synchronized boolean attempt() { + //return true if we already own the lock + //also, if nobody is waiting, grant the lock immediately + if ((currentOperationThread == Thread.currentThread()) || (currentOperationThread == null && operations.isEmpty())) { + depth++; + setCurrentOperationThread(Thread.currentThread()); + return true; + } + return false; + } + /* (non-Javadoc) + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + */ + public boolean contains(ISchedulingRule rule) { + return false; + } + /** + * Returns null if acquired and a Semaphore object otherwise. + */ + private synchronized Semaphore createSemaphore() { + return attempt() ? null : enqueue(new Semaphore(Thread.currentThread())); + } + /** + * Attempts to acquire this lock. Callers will block until this lock comes available to + * them, or until the specified delay has elapsed. + */ + private boolean doAcquire(Semaphore semaphore, long delay) throws InterruptedException { + boolean success = false; + //notify hook to service pending syncExecs before falling asleep + if (manager.aboutToWait(this.currentOperationThread)) { + //hook granted immediate access + //remove semaphore for the lock request from the queue + //do not log in graph because this thread did not really get the lock + operations.remove(semaphore); + depth++; + manager.addLockThread(currentOperationThread, this); + return true; + } + manager.addLockWaitThread(Thread.currentThread(), this); + try { + success = semaphore.acquire(delay); + } catch (InterruptedException e) { + if (DEBUG) + System.out.println("[" + Thread.currentThread() + "] Operation interrupted while waiting... :-|"); //$NON-NLS-1$ //$NON-NLS-2$ + throw e; + } + if (success) { + depth++; + updateCurrentOperation(); + } else { + //operation timed out + //remove request semaphore from queue and update graph + operations.remove(semaphore); + manager.removeLockWaitThread(Thread.currentThread(), this); + } + return success; + } + /** + * Releases this lock from the thread that used to own it. + * Grants this lock to the next thread in the queue. + */ + private synchronized void doRelease() { + //notify hook + manager.aboutToRelease(); + depth = 0; + Semaphore next = (Semaphore) operations.peek(); + setCurrentOperationThread(null); + if (next != null) + next.release(); + } + /** + * If there is another semaphore with the same runnable in the + * queue, the other is returned and the new one is not added. + */ + private synchronized Semaphore enqueue(Semaphore newSemaphore) { + Semaphore semaphore = (Semaphore) operations.get(newSemaphore); + if (semaphore == null) { + operations.enqueue(newSemaphore); + return newSemaphore; + } + return semaphore; + } + /** + * Suspend this lock by granting the lock to the next lock in the queue. + * Return the depth of the suspended lock. + */ + protected int forceRelease() { + int oldDepth = depth; + doRelease(); + return oldDepth; + } + /* (non-Javadoc) + * @see Locks.ILock#getDepth() + */ + public int getDepth() { + return depth; + } + /* (non-Javadoc) + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#isConflicting(org.eclipse.core.runtime.jobs.ISchedulingRule) + */ + public boolean isConflicting(ISchedulingRule rule) { + return rule == this; + } + /* (non-Javadoc) + * @see Locks.ILock#release() + */ + public void release() { + if (depth == 0) + return; + //only release the lock when the depth reaches zero + Assert.isTrue(depth >= 0, "Lock released too many times"); //$NON-NLS-1$ + if (--depth == 0) + doRelease(); + else + manager.removeLockThread(currentOperationThread, this); + } + /** + * If newThread is null, release this lock from its previous owner. + * If newThread is not null, grant this lock to newThread. + */ + private void setCurrentOperationThread(Thread newThread) { + if ((currentOperationThread != null) && (newThread == null)) + manager.removeLockThread(currentOperationThread, this); + this.currentOperationThread = newThread; + if (currentOperationThread != null) + manager.addLockThread(currentOperationThread, this); + } + /** + * Forces the lock to be at the given depth. + * Used when re-acquiring a suspended lock. + */ + protected void setDepth(int newDepth) { + for (int i = depth; i < newDepth; i++) { + manager.addLockThread(currentOperationThread, this); + } + this.depth = newDepth; + } + /** + * For debugging purposes only. + */ + public String toString() { + return "OrderedLock (" + number + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + } + /** + * This lock has just been granted to a new thread (the thread waited for it). + * Remove the request from the queue and update both the graph and the lock. + */ + private synchronized void updateCurrentOperation() { + operations.dequeue(); + setCurrentOperationThread(Thread.currentThread()); + } } \ No newline at end of file
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/FindSupport.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/FindSupport.java index 87b23fa..23c0b5c 100644 --- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/FindSupport.java +++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/FindSupport.java
@@ -1,3 +1,13 @@ +/******************************************************************************* + * Copyright (c) 2003 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ package org.eclipse.core.internal.runtime; import java.io.IOException;
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java index e4cf3b2..d0d1252 100644 --- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java +++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java
@@ -1,1222 +1,1219 @@ -/******************************************************************************* - * Copyright (c) 2000, 2003 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Common Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/cpl-v10.html - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.core.internal.runtime; - -import java.io.*; -import java.net.*; -import java.util.*; -import org.eclipse.core.boot.IPlatformRunnable; -import org.eclipse.core.internal.boot.*; -import org.eclipse.core.internal.jobs.JobManager; -import org.eclipse.core.runtime.*; -import org.eclipse.core.runtime.jobs.IJobManager; -import org.eclipse.osgi.service.environment.DebugOptions; -import org.eclipse.osgi.service.environment.EnvironmentInfo; -import org.osgi.framework.*; -import org.osgi.util.tracker.ServiceTracker; - -/** - * Bootstrap class for the platform. It is responsible for setting up the - * platform class loader and passing control to the actual application class - */ -public final class InternalPlatform implements IPlatform { - private BundleContext context; - private IExtensionRegistry registry; - - // registry caching mode flags - public static boolean cacheRegistry = true; - public static boolean lazyRegistryCacheLoading = true; - - private static IAdapterManager adapterManager; - - static ServiceRegistration platformRegistration; - static EnvironmentInfo infoService; - - // registry index - used to store last modified times for - // registry caching - // ASSUMPTION: Only the plugin registry in 'registry' above - // will be cached - private static Map regIndex = null; - - private static ArrayList logListeners = new ArrayList(5); - private static Map logs = new HashMap(5); - private static PlatformLogWriter platformLog = null; - private static PlatformMetaArea metaArea; - private static boolean initialized; - private static Runnable endOfInitializationHandler = null; - private static IPath location; - - private ServiceTracker debugTracker; - private DebugOptions options = null; - - // Command line args as seen by the Eclipse runtime. allArgs does NOT - // include args consumed by the underlying framework (e.g., OSGi) - private static String[] allArgs = new String[0]; - private static String[] appArgs = new String[0]; - private static String[] frameworkArgs = new String[0]; - - // the default workspace directory name - private static final String WORKSPACE = "workspace"; //$NON-NLS-1$ - - private static boolean consoleLogEnabled = false; - private static ILogListener consoleLog = null; - private static AuthorizationDatabase keyring = null; - private static String keyringFile = null; - private static String password = ""; //$NON-NLS-1$ - private static boolean splashDown = false; - private static String pluginCustomizationFile = null; - private static URL installLocation = null; - - private static PlatformMetaAreaLock metaAreaLock = null; - - /** - * Whether to perform the workspace metadata version check. - */ - private static boolean doVersionCheck = true; - - /** - * Whether to write the version.ini file on shutdown. - */ - private static boolean writeVersion = true; - - /** - * Name of the plug-in customization file (value "plugin_customization.ini") - * located in the root of the primary feature plug-in and it's - * companion nl-specific file with externalized strings (value - * "plugin_customization.properties"). The companion file can - * be contained in any nl-specific subdirectories of the primary - * feature or any fragment of this feature. - */ - private static final String PLUGIN_CUSTOMIZATION_BASE_NAME = "plugin_customization"; //$NON-NLS-1$ - private static final String PLUGIN_CUSTOMIZATION_FILE_NAME = PLUGIN_CUSTOMIZATION_BASE_NAME + ".ini"; //$NON-NLS-1$ - - // execution options - private static final String OPTION_DEBUG = PI_RUNTIME + "/debug"; //$NON-NLS-1$ - private static final String OPTION_DEBUG_SYSTEM_CONTEXT = PI_RUNTIME + "/debug/context"; //$NON-NLS-1$ - private static final String OPTION_DEBUG_SHUTDOWN = PI_RUNTIME + "/timing/shutdown"; //$NON-NLS-1$ - private static final String OPTION_DEBUG_REGISTRY = PI_RUNTIME + "/registry/debug"; //$NON-NLS-1$ - private static final String OPTION_REGISTRY_CACHE_TIMING = IPlatform.PI_RUNTIME + "/registry/cache/timing"; //$NON-NLS-1$ - private static final String OPTION_DEBUG_REGISTRY_DUMP = PI_RUNTIME + "/registry/debug/dump"; //$NON-NLS-1$ - private static final String OPTION_DEBUG_PREFERENCES = PI_RUNTIME + "/preferences/debug"; //$NON-NLS-1$ - - // command line options - private static final String ARG_APPLICATION = "-application"; //$NON-NLS-1$ - private static final String ARG_DATA = "-data"; //$NON-NLS-1$ - private static final String ARG_INSTALL = "-install"; //$NON-NLS-1$ - private static final String LOG = "-consolelog"; //$NON-NLS-1$ - private static final String KEYRING = "-keyring"; //$NON-NLS-1$ - protected static final String PASSWORD = "-password"; //$NON-NLS-1$ - private static final String NOREGISTRYCACHE = "-noregistrycache"; //$NON-NLS-1$ - private static final String NO_LAZY_REGISTRY_CACHE_LOADING = "-noLazyRegistryCacheLoading"; //$NON-NLS-1$ - private static final String PLUGIN_CUSTOMIZATION = "-plugincustomization"; //$NON-NLS-1$ - private static final String NO_PACKAGE_PREFIXES = "-noPackagePrefixes"; //$NON-NLS-1$ - private static final String NO_VERSION_CHECK = "-noversioncheck"; //$NON-NLS-1$ - private static final String CLASSLOADER_PROPERTIES = "-classloaderProperties"; //$NON-NLS-1$ - - // debug support: set in loadOptions() - public static boolean DEBUG = false; - public static boolean DEBUG_CONTEXT = false; - public static boolean DEBUG_REGISTRY = false; - public static boolean DEBUG_STARTUP = false; - public static boolean DEBUG_SHUTDOWN = false; - public static String DEBUG_REGISTRY_DUMP = null; - public static boolean DEBUG_PREFERENCES = false; - - private static final String KEY_PREFIX = "%"; //$NON-NLS-1$ - private static final String KEY_DOUBLE_PREFIX = "%%"; //$NON-NLS-1$ - - private static final String METADATA_VERSION_KEY = "org.eclipse.core.runtime"; //$NON-NLS-1$ - private static final int METADATA_VERSION_VALUE = 1; - - private static final String PLUGIN_PATH = ".plugin-path"; //$NON-NLS-1$ - - private static final InternalPlatform singleton = new InternalPlatform(); - - private IPath configMetadataLocation; - - /** - * Private constructor to block instance creation. - */ - private InternalPlatform() { - super(); - } - - public static InternalPlatform getDefault() { - return singleton; - } - - /** - * @see Platform - */ - public void addAuthorizationInfo(URL serverUrl, String realm, String authScheme, Map info) throws CoreException { - keyring.addAuthorizationInfo(serverUrl, realm, authScheme, new HashMap(info)); - keyring.save(); - } - /** - * @see Platform#addLogListener - */ - public void addLogListener(ILogListener listener) { - assertInitialized(); - synchronized (logListeners) { - // replace if already exists (Set behaviour but we use an array - // since we want to retain order) - logListeners.remove(listener); - logListeners.add(listener); - } - } - /** - * @see Platform - */ - public void addProtectionSpace(URL resourceUrl, String realm) throws CoreException { - keyring.addProtectionSpace(resourceUrl, realm); - keyring.save(); - } - /** - * @see Platform - */ - public URL asLocalURL(URL url) throws IOException { - //TODO: this is bogus - only to satisfy clients that want to resolve bundle2 URLs - if (url.getProtocol().equals("bundle2")) { - String bundleName = url.getHost(); - Bundle bundle = this.context.getBundle(bundleName.substring(0, bundleName.indexOf('_'))); - - if (bundle != null) { - URL localURL = bundle.getEntry(url.getPath()); - if (localURL != null) - return localURL; - } - return url; - } - if (!url.getProtocol().equals(PlatformURLHandler.PROTOCOL)) - return url; - URLConnection connection = url.openConnection(); - if (!(connection instanceof PlatformURLConnection)) - return url; - String file = connection.getURL().getFile(); - if (file.endsWith("/") && !file.endsWith(PlatformURLHandler.JAR_SEPARATOR)) //$NON-NLS-1$ - throw new IOException(); - return ((PlatformURLConnection) connection).getURLAsLocal(); - } - private void assertInitialized() { - //avoid the Policy.bind if assertion is true - if (!initialized) - Assert.isTrue(false, Policy.bind("meta.appNotInit")); //$NON-NLS-1$ - } - /** - * Closes the open lock file handle, and makes a silent best - * attempt to delete the file. - */ - private static synchronized void clearLockFile() { - if (metaAreaLock != null) - metaAreaLock.release(); - } - /** - * Creates a lock file in the meta-area that indicates the meta-area - * is in use, preventing other eclipse instances from concurrently - * using the same meta-area. - */ - private static synchronized void createLockFile() throws CoreException { - if (System.getProperty("org.eclipse.core.runtime.ignoreLockFile") != null) //$NON-NLS-1$ - return; - String lockLocation = metaArea.getLocation().append(PlatformMetaArea.F_LOCK_FILE).toOSString(); - metaAreaLock = new PlatformMetaAreaLock(new File(lockLocation)); - try { - if (!metaAreaLock.acquire()) { - String message = Policy.bind("meta.inUse", lockLocation); //$NON-NLS-1$ - throw new CoreException(new Status(IStatus.ERROR, IPlatform.PI_RUNTIME, IPlatform.FAILED_WRITE_METADATA, message, null)); - } - } catch (IOException e) { - String message = Policy.bind("meta.failCreateLock", lockLocation); //$NON-NLS-1$ - throw new CoreException(new Status(IStatus.ERROR, IPlatform.PI_RUNTIME, IPlatform.FAILED_WRITE_METADATA, message, e)); - } - } - - /** - * @see Platform - */ - public void endSplash() { - if (DEBUG) { - String startString = System.getProperty("eclipse.debug.startupTime"); - if (startString != null) - try { - long start = Long.parseLong(startString); - long end = System.currentTimeMillis(); - System.out.println("Startup complete: " + (end - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ - } catch (NumberFormatException e) { - //this is just debugging code -- ok to swallow exception - } - } - if (splashDown) - return; - splashDown = true; - run(endOfInitializationHandler); - } - - /** - * @see Platform - */ - public void flushAuthorizationInfo(URL serverUrl, String realm, String authScheme) throws CoreException { - keyring.flushAuthorizationInfo(serverUrl, realm, authScheme); - keyring.save(); - } - /** - * @see Platform#getAdapterManager - */ - public IAdapterManager getAdapterManager() { - assertInitialized(); - if (adapterManager == null) - adapterManager = new AdapterManager(); - return adapterManager; - } - - /** - * @see Platform - */ - public Map getAuthorizationInfo(URL serverUrl, String realm, String authScheme) { - Map info = keyring.getAuthorizationInfo(serverUrl, realm, authScheme); - return info == null ? null : new HashMap(info); - } - - public boolean getBooleanOption(String option, boolean defaultValue) { - String value = getOption(option); - return (value != null && value.equalsIgnoreCase("true")) || defaultValue; //$NON-NLS-1$ - } - - public int getIntegerOption(String option, int defaultValue) { - String value = getOption(option); - if (value == null) - return defaultValue; - try { - return Integer.parseInt(value); - } catch (NumberFormatException e) { - return defaultValue; - } - } - - public String[] getAllArgs() { - return allArgs; - } - - public String[] getAppArgs() { - return appArgs; - } - - public String[] getFrameworkArgs() { - return frameworkArgs; - } - - /** - * @see Platform - */ - public String getOption(String option) { - if (options != null) - return options.getOption(option); - return null; - } - - public IJobManager getJobManager() { - return JobManager.getInstance(); - } - - public IPath getLogFileLocation() { - return getMetaArea().getLogLocation(); - } - - /** - * @see Platform#getLocation - */ - public IPath getLocation() { - assertInitialized(); - return location; - } - /** - * Returns a log for the given plugin or <code>null</code> if none exists. - */ - public ILog getLog(Bundle bundle) { - ILog result = (ILog) logs.get(bundle); - if (result != null) - return result; - result = new Log(bundle); - logs.put(bundle, result); - return result; - } - /** - * Returns the object which defines the location and organization - * of the platform's meta area. - */ - public PlatformMetaArea getMetaArea() { - return metaArea; - } - /** - * @see Platform - */ - public String getProtectionSpace(URL resourceUrl) { - return keyring.getProtectionSpace(resourceUrl); - } - private void handleException(ISafeRunnable code, Throwable e) { - try { - if (!(e instanceof OperationCanceledException)) { - // try to figure out which plugin caused the problem. Derive this from the class - // of the code arg. Attribute to the Runtime plugin if we can't figure it out. - Bundle bundle = context.getBundleFor(code); - String pluginId = bundle.getGlobalName(); - String message = Policy.bind("meta.pluginProblems", pluginId); //$NON-NLS-1$ - IStatus status; - if (e instanceof CoreException) { - status = new MultiStatus(pluginId, PLUGIN_ERROR, message, e); - ((MultiStatus) status).merge(((CoreException) e).getStatus()); - } else { - status = new Status(Status.ERROR, pluginId, PLUGIN_ERROR, message, e); - } - getLog(bundle).log(status); - } - code.handleException(e); - } catch (Throwable th) { - th.printStackTrace(); - } - } - - public IExtensionRegistry getRegistry() { - return registry; - } - /** - * Check whether the workspace metadata version matches the expected version. - * If not, prompt the user for whether to proceed, or exit with no changes. - * Side effects: - * <ul> - * <li>remember whether to write the metadata version on exit</li> - * <li>bring down the splash screen if exiting</li> - * </ul> - * - * @return <code>true</code> to proceed, <code>false</code> to exit with no changes - */ - public boolean loaderCheckVersion() { - // if not doing the version check, then proceed with no check or prompt - boolean proceed = !doVersionCheck || checkVersionPrompt(); - // remember whether to write the version on exit; - // don't write it if the user cancelled - writeVersion = proceed; - // bring down the splash screen if the user cancelled, - // since the application won't - if (!proceed) - endSplash(); - return proceed; - } - - /** - * Internal method for finding and returning a runnable instance of the - * given class as defined in the specified plug-in. - * The returned object is initialized with the supplied arguments. - * <p> - * This method is used by the platform boot loader; is must - * not be called directly by client code. - * </p> - * @see BootLoader - */ - public IPlatformRunnable loaderGetRunnable(String applicationName) { - assertInitialized(); - IExtension extension = getRegistry().getExtension(PI_RUNTIME, PT_APPLICATIONS, applicationName); - if (extension == null) - return null; - IConfigurationElement[] configs = extension.getConfigurationElements(); - if (configs.length == 0) - return null; - try { - IConfigurationElement config = configs[0]; - return (IPlatformRunnable) config.createExecutableExtension("run"); //$NON-NLS-1$ - } catch (CoreException e) { - getLog(context.getBundle()).log(e.getStatus()); - return null; - } catch (Throwable t) { - t.printStackTrace(System.err); - return null; - } - } - - /** - * Internal method for starting up the platform. The platform is started at the - * given location. The plug-ins found at the supplied - * collection of plug-in locations are loaded into the newly started platform. - * <p> - * This method is used by the platform boot loader; is must - * not be called directly by client code. - * </p> - * @param pluginPath the list of places to look for plug-in specifications. This may - * identify individual plug-in files or directories containing directories which contain - * plug-in files. - * @param location the local filesystem location at which the newly started platform - * should be started. If the location does not contain the saved state of a platform, - * the appropriate structures are created on disk (if required). - * @param bootOptions the debug options loaded by the boot loader. If the argument - * is <code>null</code> then debugging enablement was not requested by the - * person starting the platform. - * @see BootLoader - */ - - public void start(BundleContext context) throws Exception { - this.context = context; - // TODO figure out how to do the splash. This really should be something - // that is in the OSGi implementation - endOfInitializationHandler = getSplashHandler(); - processCommandLine(infoService.getAllArgs()); - setupMetaArea(); - createLockFile(); - debugTracker = new ServiceTracker(context, DebugOptions.class.getName(), null); - debugTracker.open(); - options = (DebugOptions) debugTracker.getService(); //TODO This is not good, but is avoids problems - initializeDebugFlags(); - initialized = true; - platformLog = new PlatformLogWriter(metaArea.getLogLocation().toFile()); - addLogListener(platformLog); - if (consoleLogEnabled) { - consoleLog = new PlatformLogWriter(System.out); - addLogListener(consoleLog); - } - loadKeyring(); - platformRegistration = context.registerService(IPlatform.class.getName(), this, null); - } - private Runnable getSplashHandler() { - ServiceReference[] ref; - try { - ref = context.getServiceReferences(Runnable.class.getName(), null); - } catch (InvalidSyntaxException e) { - return null; - } - // assumes the endInitializationHandler is available as a service - // see EclipseStarter.publishSplashScreen - for (int i = 0; i < ref.length; i++) { - String name = (String) ref[i].getProperty("name"); - if (name != null && name.equals("splashscreen")) { - Runnable result = (Runnable) context.getService(ref[i]); - context.ungetService(ref[i]); - return result; - } - } - return null; - } - - /** - * Check whether the workspace metadata version matches the expected version. - * If not, prompt the user for whether to proceed, or exit with no changes. - * Side effects: none - * - * @return <code>true</code> to proceed, <code>false</code> to exit with no changes - */ - private boolean checkVersionPrompt() { - if (checkVersionNoPrompt()) - return true; - - // run the version check ui class to prompt the user - String appId = "org.eclipse.ui.versioncheck.prompt"; //$NON-NLS-1$ - IPlatformRunnable runnable = loaderGetRunnable(appId); - // If there is no UI to confirm the metadata version difference, then just proceed. - if (runnable == null) - return true; - try { - Object result = runnable.run(null); - return Boolean.TRUE.equals(result); - } catch (Exception e) { - // Fail silently since we don't have a UI, but don't proceed if we can't prompt the user. - log(new Status(IStatus.ERROR, PI_RUNTIME, 1, Policy.bind("meta.versionCheckRun", appId), null)); //$NON-NLS-1$ - return false; - } - } - - //TODO: what else must be done during the platform shutdown? See #loaderShutdown - public void stop(BundleContext bundleContext) { - assertInitialized(); - //shutdown all running jobs - JobManager.getInstance().shutdown(); - debugTracker.close(); - if (writeVersion) - writeVersion(); - clearLockFile(); - if (platformLog != null) - platformLog.shutdown(); - initialized = false; - } - - /** - * Return whether the workspace metadata version matches the expected version. - * - * @return <code>true</code> if they match, <code>false</code> if not - */ - private boolean checkVersionNoPrompt() { - File pluginsDir = metaArea.getLocation().append(PlatformMetaArea.F_PLUGIN_DATA).toFile(); - if (!pluginsDir.exists()) - return true; - - int version = -1; - File versionFile = metaArea.getVersionPath().toFile(); - if (versionFile.exists()) { - try { - // Although the version file is not spec'ed to be a Java properties file, - // it happens to follow the same format currently, so using Properties - // to read it is convenient. - Properties props = new Properties(); - FileInputStream is = new FileInputStream(versionFile); - try { - props.load(is); - } finally { - try { - is.close(); - } finally { - // ignore - } - } - String prop = props.getProperty(METADATA_VERSION_KEY); - // let any NumberFormatException be caught below - if (prop != null) - version = Integer.parseInt(prop); - } catch (Exception e) { - // Fail silently. Not a catastrophe if we can't read the version file. We don't - // want to fail execution. - log(new Status(IStatus.ERROR, PI_RUNTIME, 1, Policy.bind("meta.checkVersion", versionFile.toString()), e)); //$NON-NLS-1$ - } - } - return version == METADATA_VERSION_VALUE; - } - - /** - * Write out the version of the metadata into a known file. Overwrite - * any existing file contents. - */ - private void writeVersion() { - File versionFile = metaArea.getVersionPath().toFile(); - try { - OutputStream output = new BufferedOutputStream(new FileOutputStream(versionFile)); - try { - String versionLine = METADATA_VERSION_KEY + "=" + METADATA_VERSION_VALUE; //$NON-NLS-1$ - output.write(versionLine.getBytes("UTF-8")); //$NON-NLS-1$ - } finally { - output.close(); - } - } catch (Exception e) { - // Fail silently. Not a catastrophe if we can't write the version file. We don't - // want to fail execution. - log(new Status(IStatus.ERROR, IPlatform.PI_RUNTIME, 1, Policy.bind("meta.writeVersion", versionFile.toString()), e)); //$NON-NLS-1$ - } - } - /** - * Opens the password database (if any) initally provided to the platform at startup. - */ - private void loadKeyring() { - if (keyringFile != null) { - try { - keyring = new AuthorizationDatabase(keyringFile, password); - } catch (CoreException e) { - log(e.getStatus()); - } - if (keyring == null) { - //try deleting the file and loading again - format may have changed - new java.io.File(keyringFile).delete(); - try { - keyring = new AuthorizationDatabase(keyringFile, password); - } catch (CoreException e) { - //don't bother logging a second failure - } - } - } - if (keyring == null) - keyring = new AuthorizationDatabase(); - } - /* - * Finds and loads the options file - */ - void initializeDebugFlags() { - // load runtime options - DEBUG = getBooleanOption(OPTION_DEBUG, false); - if (DEBUG) { - DEBUG_CONTEXT = getBooleanOption(OPTION_DEBUG_SYSTEM_CONTEXT, false); - DEBUG_SHUTDOWN = getBooleanOption(OPTION_DEBUG_SHUTDOWN, false); - DEBUG_REGISTRY = getBooleanOption(OPTION_DEBUG_REGISTRY, false); - DEBUG_REGISTRY_DUMP = getOption(OPTION_DEBUG_REGISTRY_DUMP); - DEBUG_PREFERENCES = getBooleanOption(OPTION_DEBUG_PREFERENCES, false); - } - } - /** - * Notifies all listeners of the platform log. This includes the console log, if - * used, and the platform log file. All Plugin log messages get funnelled - * through here as well. - */ - public void log(final IStatus status) { - assertInitialized(); - // create array to avoid concurrent access - ILogListener[] listeners; - synchronized (logListeners) { - listeners = (ILogListener[]) logListeners.toArray(new ILogListener[logListeners.size()]); - } - for (int i = 0; i < listeners.length; i++) { - final ILogListener listener = listeners[i]; - ISafeRunnable code = new ISafeRunnable() { - public void run() throws Exception { - listener.logging(status, PI_RUNTIME); - } - public void handleException(Throwable e) { - } - }; - run(code); - } - } - - private String[] processCommandLine(String[] args) { - if (args == null) - return args; - allArgs = args; - int[] configArgs = new int[100]; - //need to initialize the first element to something that could not be an index. - configArgs[0] = -1; - int configArgIndex = 0; - for (int i = 0; i < args.length; i++) { - boolean found = false; - // check for args without parameters (i.e., a flag arg) - - // look for the log flag - if (args[i].equalsIgnoreCase(LOG)) { - consoleLogEnabled = true; - found = true; - } - - // look for the no registry cache flag - if (args[i].equalsIgnoreCase(NOREGISTRYCACHE)) { - cacheRegistry = false; - found = true; - } - - // check to see if we should NOT be lazily loading plug-in definitions from the registry cache file. - // This will be processed below. - if (args[i].equalsIgnoreCase(NO_LAZY_REGISTRY_CACHE_LOADING)) { - lazyRegistryCacheLoading = false; - found = true; - } - - // look for the flag to turn off using package prefixes - if (args[i].equalsIgnoreCase(NO_PACKAGE_PREFIXES)) { - // ignored - // PluginClassLoader.usePackagePrefixes = false; - found = true; - } - - // look for the flag to turn off the workspace metadata version check - if (args[i].equalsIgnoreCase(NO_VERSION_CHECK)) { - doVersionCheck = false; - found = true; - } - - // this option (may have and argument) comes from InternalBootLoader.processCommandLine - if (args[i].equalsIgnoreCase(CLASSLOADER_PROPERTIES)) { - // ignored - found = true; - } - - // done checking for args. Remember where an arg was found - if (found) { - configArgs[configArgIndex++] = i; - continue; - } - // check for args with parameters - if (i == args.length - 1 || args[i + 1].startsWith("-")) //$NON-NLS-1$ - continue; - String arg = args[++i]; - - // look for the default data location - if (args[i - 1].equalsIgnoreCase(ARG_DATA)) { - location = new Path(arg); - found = true; - } - - // look for the keyring file - if (args[i - 1].equalsIgnoreCase(KEYRING)) { - keyringFile = arg; - found = true; - } - - // look for the user password. - if (args[i - 1].equalsIgnoreCase(PASSWORD)) { - password = arg; - found = true; - } - - // look for the application to run. - if (args[i - 1].equalsIgnoreCase(ARG_APPLICATION)) { - System.setProperty("eclipse.application", arg); //$NON-NLS-1$ - found = true; - } - - // look for the plug-in customization file - if (args[i - 1].equalsIgnoreCase(PLUGIN_CUSTOMIZATION)) { - pluginCustomizationFile = arg; - found = true; - } - - if (args[i - 1].equalsIgnoreCase(CLASSLOADER_PROPERTIES)) { - // ignored - found = true; - } - - // done checking for args. Remember where an arg was found - if (found) { - configArgs[configArgIndex++] = i - 1; - configArgs[configArgIndex++] = i; - } - } - - // remove all the arguments consumed by this argument parsing - if (configArgIndex == 0) { - appArgs = args; - return args; - } - appArgs = new String[args.length - configArgIndex]; - frameworkArgs = new String[configArgIndex]; - configArgIndex = 0; - int j = 0; - int k = 0; - for (int i = 0; i < args.length; i++) { - if (i == configArgs[configArgIndex]) { - frameworkArgs[k++] = args[i]; - configArgIndex++; - } else - appArgs[j++] = args[i]; - } - return appArgs; - } - - /** - * @see Platform#removeLogListener - */ - public void removeLogListener(ILogListener listener) { - assertInitialized(); - synchronized (logListeners) { - logListeners.remove(listener); - } - } - /** - * @see Platform - */ - public URL resolve(URL url) throws IOException { - if (!url.getProtocol().equals(PlatformURLHandler.PROTOCOL)) - return url; - URLConnection connection = url.openConnection(); - if (connection instanceof PlatformURLConnection) - return ((PlatformURLConnection) connection).getResolvedURL(); - else - return url; - } - public void run(ISafeRunnable code) { - Assert.isNotNull(code); - try { - code.run(); - } catch (Exception e) { - handleException(code, e); - } catch (LinkageError e) { - handleException(code, e); - } - } - private void run(Runnable handler) { - // run end-of-initialization handler - if (handler == null) - return; - - final Runnable finalHandler = handler; - ISafeRunnable code = new ISafeRunnable() { - public void run() throws Exception { - finalHandler.run(); - } - public void handleException(Throwable e) { - // just continue ... the exception has already been logged by - // the platform (see handleException(ISafeRunnable) - } - }; - run(code); - } - public void setOption(String option, String value) { - if (options != null) - options.setOption(option, value); - } - private void setupMetaArea() throws CoreException { - // if a platform location was not found in the arguments, compute one. - if (location == null) { - // Default location for the workspace is <user.dir>/workspace/ - location = new Path(System.getProperty("user.dir")).append(WORKSPACE); //$NON-NLS-1$ - } - if (!location.isAbsolute()) - location = new Path(System.getProperty("user.dir")).append(location); //$NON-NLS-1$ - // must create the meta area first as it defines all the other locations. - if (location.toFile().exists()) { - if (!location.toFile().isDirectory()) { - String message = Policy.bind("meta.notDir", location.toString()); //$NON-NLS-1$ - throw new CoreException(new Status(IStatus.ERROR, PI_RUNTIME, FAILED_WRITE_METADATA, message, null)); - } - } - //try infer the device if there isn't one (windows) - if (location.getDevice()==null) - location = new Path(location.toFile().getAbsolutePath()); - metaArea = new PlatformMetaArea(location); - metaArea.createLocation(); - if (keyringFile == null) - keyringFile = metaArea.getLocation().append(PlatformMetaArea.F_KEYRING).toOSString(); - } - - public void addLastModifiedTime(String pathKey, long lastModTime) { - if (regIndex == null) - regIndex = new HashMap(30); - regIndex.put(pathKey, new Long(lastModTime)); - } - public Map getRegIndex() { - return regIndex; - } - public void clearRegIndex() { - regIndex = null; - } - - /** - * Look for the companion preference translation file for a group - * of preferences. This method will attempt to find a companion - * ".properties" file first. This companion file can be in an - * nl-specific directory for this plugin or any of its fragments or - * it can be in the root of this plugin or the root of any of the - * plugin's fragments. This properties file can be used to translate - * preference values. - * - * @param pluginDescriptor the descriptor of the plugin - * who has the preferences - * @param basePrefFileName the base name of the preference file - * This base will be used to construct the name of the - * companion translation file. - * Example: If basePrefFileName is "plugin_customization", - * the preferences are in "plugin_customization.ini" and - * the translations are found in - * "plugin_customization.properties". - * @return the properties file - * - * @since 2.0 - */ - public Properties getPreferenceTranslator(String uniqueIdentifier, String basePrefFileName) { - return new Properties(); - } - - /** - * Takes a preference value and a related resource bundle and - * returns the translated version of this value (if one exists). - * - * @param value the preference value for potential translation - * @param bundle the bundle containing the translated values - * - * @since 2.0 - */ - public String translatePreference(String value, Properties props) { - value = value.trim(); - if (props == null || value.startsWith(KEY_DOUBLE_PREFIX)) - return value; - if (value.startsWith(KEY_PREFIX)) { - - int ix = value.indexOf(" "); //$NON-NLS-1$ - String key = ix == -1 ? value : value.substring(0, ix); - String dflt = ix == -1 ? value : value.substring(ix + 1); - return props.getProperty(key.substring(1), dflt); - } - return value; - } - - /** - * Applies primary feature-specific overrides to default preferences for the - * plug-in with the given id. - * <p> - * Note that by the time this method is called, the default settings - * for the plug-in itself should have already have been filled in. - * </p> - * - * @param id the unique identifier of the plug-in - * @param preferences the preference store for the specified plug-in - * - * @since 2.0 - */ - public void applyPrimaryFeaturePluginDefaultOverrides(String id, Preferences preferences) { - } - - /** - * Applies command line-supplied overrides to default preferences for the - * plug-in with the given id. - * <p> - * Note that by the time this method is called, the default settings - * for the plug-in itself should have already have been filled in, along - * with any default overrides supplied by the primary feature. - * </p> - * - * @param id the unique identifier of the plug-in - * @param preferences the preference store for the specified plug-in - * - * @since 2.0 - */ - public void applyCommandLinePluginDefaultOverrides(String id, Preferences preferences) { - - if (pluginCustomizationFile == null) { - // no command line overrides to process - if (DEBUG_PREFERENCES) { - System.out.println("Command line argument -pluginCustomization not used."); //$NON-NLS-1$ - } - return; - } - - try { - URL pluginCustomizationURL = new File(pluginCustomizationFile).toURL(); - if (DEBUG_PREFERENCES) { - System.out.println("Loading preferences from " + pluginCustomizationURL); //$NON-NLS-1$ - } - applyPluginDefaultOverrides(pluginCustomizationURL, id, preferences, null); - } catch (MalformedURLException e) { - // fail silently - if (DEBUG_PREFERENCES) { - System.out.println("MalformedURLException creating URL for plugin customization file " //$NON-NLS-1$ - +pluginCustomizationFile); - e.printStackTrace(); - } - return; - } - } - - /** - * Applies overrides to default preferences for the plug-in with the given id. - * The data is contained in the <code>java.io.Properties</code> style file at - * the given URL. The property names consist of "/'-separated plug-in id and - * name of preference; e.g., "com.example.myplugin/mypref". - * - * @param propertiesURL the URL of a <code>java.io.Properties</code> style file - * @param id the unique identifier of the plug-in - * @param preferences the preference store for the specified plug-in - * - * @since 2.0 - */ - private void applyPluginDefaultOverrides(URL propertiesURL, String id, Preferences preferences, Properties props) { - - // read the java.io.Properties file at the given URL - Properties overrides = new Properties(); - SafeFileInputStream in = null; - - try { - File inFile = new File(propertiesURL.getFile()); - if (!inFile.exists()) { - // We don't have a preferences file to worry about - if (DEBUG_PREFERENCES) { - System.out.println("Preference file " + //$NON-NLS-1$ - propertiesURL + " not found."); //$NON-NLS-1$ - } - return; - } - - in = new SafeFileInputStream(inFile); - if (in == null) { - // fail quietly - if (DEBUG_PREFERENCES) { - System.out.println("Failed to open " + //$NON-NLS-1$ - propertiesURL); - } - return; - } - overrides.load(in); - } catch (IOException e) { - // cannot read ini file - fail silently - if (DEBUG_PREFERENCES) { - System.out.println("IOException reading preference file " + //$NON-NLS-1$ - propertiesURL); - e.printStackTrace(); - } - return; - } finally { - try { - if (in != null) { - in.close(); - } - } catch (IOException e) { - // ignore problems closing file - if (DEBUG_PREFERENCES) { - System.out.println("IOException closing preference file " + //$NON-NLS-1$ - propertiesURL); - e.printStackTrace(); - } - } - } - - for (Iterator it = overrides.entrySet().iterator(); it.hasNext();) { - Map.Entry entry = (Map.Entry) it.next(); - String qualifiedKey = (String) entry.getKey(); - // Keys consist of "/'-separated plug-in id and name of preference - // e.g., "com.example.myplugin/mypref" - int s = qualifiedKey.indexOf('/'); - if (s < 0 || s == 0 || s == qualifiedKey.length() - 1) { - // skip mangled entry - continue; - } - // plug-in id is non-empty string before "/" - String pluginId = qualifiedKey.substring(0, s); - if (pluginId.equals(id)) { - // override property in the given plug-in - // plig-in-specified property name is non-empty string after "/" - String propertyName = qualifiedKey.substring(s + 1); - String value = (String) entry.getValue(); - value = translatePreference(value, props); - preferences.setDefault(propertyName, value); - } - } - if (DEBUG_PREFERENCES) { - System.out.println("Preferences now set as follows:"); //$NON-NLS-1$ - String[] prefNames = preferences.propertyNames(); - for (int i = 0; i < prefNames.length; i++) { - String value = preferences.getString(prefNames[i]); - System.out.println("\t" + prefNames[i] + " = " + value); //$NON-NLS-1$ //$NON-NLS-2$ - } - prefNames = preferences.defaultPropertyNames(); - for (int i = 0; i < prefNames.length; i++) { - String value = preferences.getDefaultString(prefNames[i]); - System.out.println("\tDefault values: " + prefNames[i] + " = " + value); //$NON-NLS-1$ //$NON-NLS-2$ - } - } - } - public void setExtensionRegistry(IExtensionRegistry value) { - registry = value; - } - public BundleContext getBundleContext() { - return context; - } - public Bundle getBundle(String id) { - return getBundleContext().getBundle(id); - } - - public URL getInstallURL() { - if (installLocation == null) - try { - installLocation = new URL((String) System.getProperty("eclipse.installURL")); - } catch (MalformedURLException e) { - //This can't fail because eclipse.installURL has been set with a valid URL in BootLoader - } - return installLocation; - } - public EnvironmentInfo getEnvironmentInfoService() { - return infoService; - } - /** - * @deprecated either look for the IPlatform service or see if the runtime bundle is started. - */ - public boolean isRunning() { - int state = context.getBundle(PI_RUNTIME).getState(); - return state == Bundle.ACTIVE; - } - - /* - * This method is retained for R1.0 compatibility because it is defined as API. - * It's function matches the API description (returns <code>null</code> when - * argument URL is <code>null</code> or cannot be read). - */ - public URL[] getPluginPath(URL pluginPathLocation /*R1.0 compatibility*/ - ) { - InputStream input = null; - // first try and see if the given plugin path location exists. - if (pluginPathLocation == null) - return null; - try { - input = pluginPathLocation.openStream(); - } catch (IOException e) { - //fall through - } - - // if the given path was null or did not exist, look for a plugin path - // definition in the install location. - if (input == null) - try { - URL url = new URL(PlatformURLBaseConnection.PLATFORM_URL_STRING + PLUGIN_PATH); - input = url.openStream(); - } catch (MalformedURLException e) { - //fall through - } catch (IOException e) { - //fall through - } - - // nothing was found at the supplied location or in the install location - if (input == null) - return null; - // if we found a plugin path definition somewhere so read it and close the location. - URL[] result = null; - try { - try { - result = readPluginPath(input); - } finally { - input.close(); - } - } catch (IOException e) { - //let it return null on failure to read - } - return result; - } - - private URL[] readPluginPath(InputStream input) { - Properties ini = new Properties(); - try { - ini.load(input); - } catch (IOException e) { - return null; - } - Vector result = new Vector(5); - for (Enumeration groups = ini.propertyNames(); groups.hasMoreElements();) { - String group = (String) groups.nextElement(); - for (StringTokenizer entries = new StringTokenizer(ini.getProperty(group), ";"); entries.hasMoreElements();) { //$NON-NLS-1$ - String entry = (String) entries.nextElement(); - if (!entry.equals("")) //$NON-NLS-1$ - try { - result.addElement(new URL(entry)); - } catch (MalformedURLException e) { - //intentionally ignore bad URLs - System.err.println(Policy.bind("ignore.plugin", entry)); //$NON-NLS-1$ - } - } - } - return (URL[]) result.toArray(new URL[result.size()]); - } - public IPath getConfigurationMetadataLocation() { - if (configMetadataLocation == null) - configMetadataLocation = new Path(System.getProperty("osgi.configuration.area")); - return configMetadataLocation; - } - - public IPath getStateLocation(Bundle bundle, boolean create) { - assertInitialized(); - IPath result = metaArea.getStateLocation(bundle); - if (create) - result.toFile().mkdirs(); - return result; - } - public URL find(Bundle b, IPath path) { - return FindSupport.find(b, path); - } - public URL find(Bundle bundle, IPath path, Map override) { - return FindSupport.find(bundle, path, override); - } - public InputStream openStream(Bundle bundle, IPath file) throws IOException { - return FindSupport.openStream(bundle, file, false); - } - public InputStream openStream(Bundle bundle, IPath file, boolean localized) throws IOException { - return FindSupport.openStream(bundle, file, localized); - } - public IPath getStateLocation(Bundle bundle) { - return getStateLocation(bundle, true); - } - public ResourceBundle getResourceBundle(Bundle bundle) throws MissingResourceException { - //TODO - throw new NoSuchMethodError("getResourceBundleString"); - } - public String getResourceString(Bundle bundle, String value) { - //TODO - throw new NoSuchMethodError("getResourceBundleString"); - } - public String getResourceString(Bundle bundle, String value, ResourceBundle resourceBundle) { - //TODO - throw new NoSuchMethodError("getResourceBundleString"); - } -} +/******************************************************************************* + * Copyright (c) 2000, 2003 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.runtime; + +import java.io.*; +import java.net.*; +import java.util.*; +import org.eclipse.core.boot.IPlatformRunnable; +import org.eclipse.core.internal.boot.*; +import org.eclipse.core.internal.jobs.JobManager; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.IJobManager; +import org.eclipse.osgi.service.environment.DebugOptions; +import org.eclipse.osgi.service.environment.EnvironmentInfo; +import org.osgi.framework.*; +import org.osgi.util.tracker.ServiceTracker; + +/** + * Bootstrap class for the platform. It is responsible for setting up the + * platform class loader and passing control to the actual application class + */ +public final class InternalPlatform implements IPlatform { + private BundleContext context; + private IExtensionRegistry registry; + + // registry caching mode flags + public static boolean cacheRegistry = true; + public static boolean lazyRegistryCacheLoading = true; + + private static IAdapterManager adapterManager; + + static ServiceRegistration platformRegistration; + static EnvironmentInfo infoService; + + // registry index - used to store last modified times for + // registry caching + // ASSUMPTION: Only the plugin registry in 'registry' above + // will be cached + private static Map regIndex = null; + + private static ArrayList logListeners = new ArrayList(5); + private static Map logs = new HashMap(5); + private static PlatformLogWriter platformLog = null; + private static PlatformMetaArea metaArea; + private static boolean initialized; + private static Runnable endOfInitializationHandler = null; + private static IPath location; + + private ServiceTracker debugTracker; + private DebugOptions options = null; + + // Command line args as seen by the Eclipse runtime. allArgs does NOT + // include args consumed by the underlying framework (e.g., OSGi) + private static String[] allArgs = new String[0]; + private static String[] appArgs = new String[0]; + private static String[] frameworkArgs = new String[0]; + + // the default workspace directory name + private static final String WORKSPACE = "workspace"; //$NON-NLS-1$ + + private static boolean consoleLogEnabled = false; + private static ILogListener consoleLog = null; + private static AuthorizationDatabase keyring = null; + private static String keyringFile = null; + private static String password = ""; //$NON-NLS-1$ + private static boolean splashDown = false; + private static String pluginCustomizationFile = null; + private static URL installLocation = null; + + private static PlatformMetaAreaLock metaAreaLock = null; + + /** + * Whether to perform the workspace metadata version check. + */ + private static boolean doVersionCheck = true; + + /** + * Whether to write the version.ini file on shutdown. + */ + private static boolean writeVersion = true; + + /** + * Name of the plug-in customization file (value "plugin_customization.ini") + * located in the root of the primary feature plug-in and it's + * companion nl-specific file with externalized strings (value + * "plugin_customization.properties"). The companion file can + * be contained in any nl-specific subdirectories of the primary + * feature or any fragment of this feature. + */ + private static final String PLUGIN_CUSTOMIZATION_BASE_NAME = "plugin_customization"; //$NON-NLS-1$ + private static final String PLUGIN_CUSTOMIZATION_FILE_NAME = PLUGIN_CUSTOMIZATION_BASE_NAME + ".ini"; //$NON-NLS-1$ + + // execution options + private static final String OPTION_DEBUG = PI_RUNTIME + "/debug"; //$NON-NLS-1$ + private static final String OPTION_DEBUG_SYSTEM_CONTEXT = PI_RUNTIME + "/debug/context"; //$NON-NLS-1$ + private static final String OPTION_DEBUG_SHUTDOWN = PI_RUNTIME + "/timing/shutdown"; //$NON-NLS-1$ + private static final String OPTION_DEBUG_REGISTRY = PI_RUNTIME + "/registry/debug"; //$NON-NLS-1$ + private static final String OPTION_REGISTRY_CACHE_TIMING = IPlatform.PI_RUNTIME + "/registry/cache/timing"; //$NON-NLS-1$ + private static final String OPTION_DEBUG_REGISTRY_DUMP = PI_RUNTIME + "/registry/debug/dump"; //$NON-NLS-1$ + private static final String OPTION_DEBUG_PREFERENCES = PI_RUNTIME + "/preferences/debug"; //$NON-NLS-1$ + + // command line options + private static final String ARG_APPLICATION = "-application"; //$NON-NLS-1$ + private static final String ARG_DATA = "-data"; //$NON-NLS-1$ + private static final String ARG_INSTALL = "-install"; //$NON-NLS-1$ + private static final String LOG = "-consolelog"; //$NON-NLS-1$ + private static final String KEYRING = "-keyring"; //$NON-NLS-1$ + protected static final String PASSWORD = "-password"; //$NON-NLS-1$ + private static final String NOREGISTRYCACHE = "-noregistrycache"; //$NON-NLS-1$ + private static final String NO_LAZY_REGISTRY_CACHE_LOADING = "-noLazyRegistryCacheLoading"; //$NON-NLS-1$ + private static final String PLUGIN_CUSTOMIZATION = "-plugincustomization"; //$NON-NLS-1$ + private static final String NO_PACKAGE_PREFIXES = "-noPackagePrefixes"; //$NON-NLS-1$ + private static final String NO_VERSION_CHECK = "-noversioncheck"; //$NON-NLS-1$ + private static final String CLASSLOADER_PROPERTIES = "-classloaderProperties"; //$NON-NLS-1$ + + // debug support: set in loadOptions() + public static boolean DEBUG = false; + public static boolean DEBUG_CONTEXT = false; + public static boolean DEBUG_REGISTRY = false; + public static boolean DEBUG_STARTUP = false; + public static boolean DEBUG_SHUTDOWN = false; + public static String DEBUG_REGISTRY_DUMP = null; + public static boolean DEBUG_PREFERENCES = false; + + private static final String KEY_PREFIX = "%"; //$NON-NLS-1$ + private static final String KEY_DOUBLE_PREFIX = "%%"; //$NON-NLS-1$ + + private static final String METADATA_VERSION_KEY = "org.eclipse.core.runtime"; //$NON-NLS-1$ + private static final int METADATA_VERSION_VALUE = 1; + + private static final String PLUGIN_PATH = ".plugin-path"; //$NON-NLS-1$ + + private static final InternalPlatform singleton = new InternalPlatform(); + + private IPath configMetadataLocation; + + /** + * Private constructor to block instance creation. + */ + private InternalPlatform() { + super(); + } + + public static InternalPlatform getDefault() { + return singleton; + } + + /** + * @see Platform + */ + public void addAuthorizationInfo(URL serverUrl, String realm, String authScheme, Map info) throws CoreException { + keyring.addAuthorizationInfo(serverUrl, realm, authScheme, new HashMap(info)); + keyring.save(); + } + /** + * @see Platform#addLogListener + */ + public void addLogListener(ILogListener listener) { + assertInitialized(); + synchronized (logListeners) { + // replace if already exists (Set behaviour but we use an array + // since we want to retain order) + logListeners.remove(listener); + logListeners.add(listener); + } + } + /** + * @see Platform + */ + public void addProtectionSpace(URL resourceUrl, String realm) throws CoreException { + keyring.addProtectionSpace(resourceUrl, realm); + keyring.save(); + } + /** + * @see Platform + */ + public URL asLocalURL(URL url) throws IOException { + //TODO: this is bogus - only to satisfy clients that want to resolve bundle2 URLs + if (url.getProtocol().equals("bundle2")) { //$NON-NLS-1$ + String bundleName = url.getHost(); + Bundle bundle = this.context.getBundle(bundleName.substring(0, bundleName.indexOf('_'))); + + if (bundle != null) { + URL localURL = bundle.getEntry(url.getPath()); + if (localURL != null) + return localURL; + } + return url; + } + if (!url.getProtocol().equals(PlatformURLHandler.PROTOCOL)) + return url; + URLConnection connection = url.openConnection(); + if (!(connection instanceof PlatformURLConnection)) + return url; + String file = connection.getURL().getFile(); + if (file.endsWith("/") && !file.endsWith(PlatformURLHandler.JAR_SEPARATOR)) //$NON-NLS-1$ + throw new IOException(); + return ((PlatformURLConnection) connection).getURLAsLocal(); + } + private void assertInitialized() { + //avoid the Policy.bind if assertion is true + if (!initialized) + Assert.isTrue(false, Policy.bind("meta.appNotInit")); //$NON-NLS-1$ + } + /** + * Closes the open lock file handle, and makes a silent best + * attempt to delete the file. + */ + private static synchronized void clearLockFile() { + if (metaAreaLock != null) + metaAreaLock.release(); + } + /** + * Creates a lock file in the meta-area that indicates the meta-area + * is in use, preventing other eclipse instances from concurrently + * using the same meta-area. + */ + private static synchronized void createLockFile() throws CoreException { + if (System.getProperty("org.eclipse.core.runtime.ignoreLockFile") != null) //$NON-NLS-1$ + return; + String lockLocation = metaArea.getLocation().append(PlatformMetaArea.F_LOCK_FILE).toOSString(); + metaAreaLock = new PlatformMetaAreaLock(new File(lockLocation)); + try { + if (!metaAreaLock.acquire()) { + String message = Policy.bind("meta.inUse", lockLocation); //$NON-NLS-1$ + throw new CoreException(new Status(IStatus.ERROR, IPlatform.PI_RUNTIME, IPlatform.FAILED_WRITE_METADATA, message, null)); + } + } catch (IOException e) { + String message = Policy.bind("meta.failCreateLock", lockLocation); //$NON-NLS-1$ + throw new CoreException(new Status(IStatus.ERROR, IPlatform.PI_RUNTIME, IPlatform.FAILED_WRITE_METADATA, message, e)); + } + } + + /** + * @see Platform + */ + public void endSplash() { + if (DEBUG) { + String startString = System.getProperty("eclipse.debug.startupTime"); //$NON-NLS-1$ + if (startString != null) + try { + long start = Long.parseLong(startString); + long end = System.currentTimeMillis(); + System.out.println("Startup complete: " + (end - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } catch (NumberFormatException e) { + //this is just debugging code -- ok to swallow exception + } + } + if (splashDown) + return; + splashDown = true; + run(endOfInitializationHandler); + } + + /** + * @see Platform + */ + public void flushAuthorizationInfo(URL serverUrl, String realm, String authScheme) throws CoreException { + keyring.flushAuthorizationInfo(serverUrl, realm, authScheme); + keyring.save(); + } + /** + * @see Platform#getAdapterManager + */ + public IAdapterManager getAdapterManager() { + assertInitialized(); + if (adapterManager == null) + adapterManager = new AdapterManager(); + return adapterManager; + } + + /** + * @see Platform + */ + public Map getAuthorizationInfo(URL serverUrl, String realm, String authScheme) { + Map info = keyring.getAuthorizationInfo(serverUrl, realm, authScheme); + return info == null ? null : new HashMap(info); + } + + public boolean getBooleanOption(String option, boolean defaultValue) { + String value = getOption(option); + return (value != null && value.equalsIgnoreCase("true")) || defaultValue; //$NON-NLS-1$ + } + + public int getIntegerOption(String option, int defaultValue) { + String value = getOption(option); + if (value == null) + return defaultValue; + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + public String[] getAllArgs() { + return allArgs; + } + + public String[] getAppArgs() { + return appArgs; + } + + public String[] getFrameworkArgs() { + return frameworkArgs; + } + + /** + * @see Platform + */ + public String getOption(String option) { + if (options != null) + return options.getOption(option); + return null; + } + + public IJobManager getJobManager() { + return JobManager.getInstance(); + } + + public IPath getLogFileLocation() { + return getMetaArea().getLogLocation(); + } + + /** + * @see Platform#getLocation + */ + public IPath getLocation() { + assertInitialized(); + return location; + } + /** + * Returns a log for the given plugin or <code>null</code> if none exists. + */ + public ILog getLog(Bundle bundle) { + ILog result = (ILog) logs.get(bundle); + if (result != null) + return result; + result = new Log(bundle); + logs.put(bundle, result); + return result; + } + /** + * Returns the object which defines the location and organization + * of the platform's meta area. + */ + public PlatformMetaArea getMetaArea() { + return metaArea; + } + /** + * @see Platform + */ + public String getProtectionSpace(URL resourceUrl) { + return keyring.getProtectionSpace(resourceUrl); + } + private void handleException(ISafeRunnable code, Throwable e) { + if (!(e instanceof OperationCanceledException)) { + String pluginId = PI_RUNTIME; + String message = Policy.bind("meta.pluginProblems", pluginId); //$NON-NLS-1$ + IStatus status; + if (e instanceof CoreException) { + status = new MultiStatus(pluginId, IPlatform.PLUGIN_ERROR, message, e); + ((MultiStatus)status).merge(((CoreException)e).getStatus()); + } else { + status = new Status(IStatus.ERROR, pluginId, IPlatform.PLUGIN_ERROR, message, e); + } + log(status); //$NON-NLS-1$ + } + code.handleException(e); + } + + public IExtensionRegistry getRegistry() { + return registry; + } + /** + * Check whether the workspace metadata version matches the expected version. + * If not, prompt the user for whether to proceed, or exit with no changes. + * Side effects: + * <ul> + * <li>remember whether to write the metadata version on exit</li> + * <li>bring down the splash screen if exiting</li> + * </ul> + * + * @return <code>true</code> to proceed, <code>false</code> to exit with no changes + */ + public boolean loaderCheckVersion() { + // if not doing the version check, then proceed with no check or prompt + boolean proceed = !doVersionCheck || checkVersionPrompt(); + // remember whether to write the version on exit; + // don't write it if the user cancelled + writeVersion = proceed; + // bring down the splash screen if the user cancelled, + // since the application won't + if (!proceed) + endSplash(); + return proceed; + } + + /** + * Internal method for finding and returning a runnable instance of the + * given class as defined in the specified plug-in. + * The returned object is initialized with the supplied arguments. + * <p> + * This method is used by the platform boot loader; is must + * not be called directly by client code. + * </p> + * @see BootLoader + */ + public IPlatformRunnable loaderGetRunnable(String applicationName) { + assertInitialized(); + IExtension extension = getRegistry().getExtension(PI_RUNTIME, PT_APPLICATIONS, applicationName); + if (extension == null) + return null; + IConfigurationElement[] configs = extension.getConfigurationElements(); + if (configs.length == 0) + return null; + try { + IConfigurationElement config = configs[0]; + return (IPlatformRunnable) config.createExecutableExtension("run"); //$NON-NLS-1$ + } catch (CoreException e) { + getLog(context.getBundle()).log(e.getStatus()); + return null; + } catch (Throwable t) { + t.printStackTrace(System.err); + return null; + } + } + + /** + * Internal method for starting up the platform. The platform is started at the + * given location. The plug-ins found at the supplied + * collection of plug-in locations are loaded into the newly started platform. + * <p> + * This method is used by the platform boot loader; is must + * not be called directly by client code. + * </p> + * @param pluginPath the list of places to look for plug-in specifications. This may + * identify individual plug-in files or directories containing directories which contain + * plug-in files. + * @param location the local filesystem location at which the newly started platform + * should be started. If the location does not contain the saved state of a platform, + * the appropriate structures are created on disk (if required). + * @param bootOptions the debug options loaded by the boot loader. If the argument + * is <code>null</code> then debugging enablement was not requested by the + * person starting the platform. + * @see BootLoader + */ + + public void start(BundleContext context) throws Exception { + this.context = context; + // TODO figure out how to do the splash. This really should be something + // that is in the OSGi implementation + endOfInitializationHandler = getSplashHandler(); + processCommandLine(infoService.getAllArgs()); + setupMetaArea(); + createLockFile(); + debugTracker = new ServiceTracker(context, DebugOptions.class.getName(), null); + debugTracker.open(); + options = (DebugOptions) debugTracker.getService(); //TODO This is not good, but is avoids problems + initializeDebugFlags(); + initialized = true; + platformLog = new PlatformLogWriter(metaArea.getLogLocation().toFile()); + addLogListener(platformLog); + if (consoleLogEnabled) { + consoleLog = new PlatformLogWriter(System.out); + addLogListener(consoleLog); + } + loadKeyring(); + platformRegistration = context.registerService(IPlatform.class.getName(), this, null); + } + private Runnable getSplashHandler() { + ServiceReference[] ref; + try { + ref = context.getServiceReferences(Runnable.class.getName(), null); + } catch (InvalidSyntaxException e) { + return null; + } + // assumes the endInitializationHandler is available as a service + // see EclipseStarter.publishSplashScreen + for (int i = 0; i < ref.length; i++) { + String name = (String) ref[i].getProperty("name"); //$NON-NLS-1$ + if (name != null && name.equals("splashscreen")) { //$NON-NLS-1$ + Runnable result = (Runnable) context.getService(ref[i]); + context.ungetService(ref[i]); + return result; + } + } + return null; + } + + /** + * Check whether the workspace metadata version matches the expected version. + * If not, prompt the user for whether to proceed, or exit with no changes. + * Side effects: none + * + * @return <code>true</code> to proceed, <code>false</code> to exit with no changes + */ + private boolean checkVersionPrompt() { + if (checkVersionNoPrompt()) + return true; + + // run the version check ui class to prompt the user + String appId = "org.eclipse.ui.versioncheck.prompt"; //$NON-NLS-1$ + IPlatformRunnable runnable = loaderGetRunnable(appId); + // If there is no UI to confirm the metadata version difference, then just proceed. + if (runnable == null) + return true; + try { + Object result = runnable.run(null); + return Boolean.TRUE.equals(result); + } catch (Exception e) { + // Fail silently since we don't have a UI, but don't proceed if we can't prompt the user. + log(new Status(IStatus.ERROR, PI_RUNTIME, 1, Policy.bind("meta.versionCheckRun", appId), null)); //$NON-NLS-1$ + return false; + } + } + + //TODO: what else must be done during the platform shutdown? See #loaderShutdown + public void stop(BundleContext bundleContext) { + assertInitialized(); + //shutdown all running jobs + JobManager.getInstance().shutdown(); + debugTracker.close(); + if (writeVersion) + writeVersion(); + clearLockFile(); + if (platformLog != null) + platformLog.shutdown(); + initialized = false; + } + + /** + * Return whether the workspace metadata version matches the expected version. + * + * @return <code>true</code> if they match, <code>false</code> if not + */ + private boolean checkVersionNoPrompt() { + File pluginsDir = metaArea.getLocation().append(PlatformMetaArea.F_PLUGIN_DATA).toFile(); + if (!pluginsDir.exists()) + return true; + + int version = -1; + File versionFile = metaArea.getVersionPath().toFile(); + if (versionFile.exists()) { + try { + // Although the version file is not spec'ed to be a Java properties file, + // it happens to follow the same format currently, so using Properties + // to read it is convenient. + Properties props = new Properties(); + FileInputStream is = new FileInputStream(versionFile); + try { + props.load(is); + } finally { + try { + is.close(); + } finally { + // ignore + } + } + String prop = props.getProperty(METADATA_VERSION_KEY); + // let any NumberFormatException be caught below + if (prop != null) + version = Integer.parseInt(prop); + } catch (Exception e) { + // Fail silently. Not a catastrophe if we can't read the version file. We don't + // want to fail execution. + log(new Status(IStatus.ERROR, PI_RUNTIME, 1, Policy.bind("meta.checkVersion", versionFile.toString()), e)); //$NON-NLS-1$ + } + } + return version == METADATA_VERSION_VALUE; + } + + /** + * Write out the version of the metadata into a known file. Overwrite + * any existing file contents. + */ + private void writeVersion() { + File versionFile = metaArea.getVersionPath().toFile(); + try { + OutputStream output = new BufferedOutputStream(new FileOutputStream(versionFile)); + try { + String versionLine = METADATA_VERSION_KEY + "=" + METADATA_VERSION_VALUE; //$NON-NLS-1$ + output.write(versionLine.getBytes("UTF-8")); //$NON-NLS-1$ + } finally { + output.close(); + } + } catch (Exception e) { + // Fail silently. Not a catastrophe if we can't write the version file. We don't + // want to fail execution. + log(new Status(IStatus.ERROR, IPlatform.PI_RUNTIME, 1, Policy.bind("meta.writeVersion", versionFile.toString()), e)); //$NON-NLS-1$ + } + } + /** + * Opens the password database (if any) initally provided to the platform at startup. + */ + private void loadKeyring() { + if (keyringFile != null) { + try { + keyring = new AuthorizationDatabase(keyringFile, password); + } catch (CoreException e) { + log(e.getStatus()); + } + if (keyring == null) { + //try deleting the file and loading again - format may have changed + new java.io.File(keyringFile).delete(); + try { + keyring = new AuthorizationDatabase(keyringFile, password); + } catch (CoreException e) { + //don't bother logging a second failure + } + } + } + if (keyring == null) + keyring = new AuthorizationDatabase(); + } + /* + * Finds and loads the options file + */ + void initializeDebugFlags() { + // load runtime options + DEBUG = getBooleanOption(OPTION_DEBUG, false); + if (DEBUG) { + DEBUG_CONTEXT = getBooleanOption(OPTION_DEBUG_SYSTEM_CONTEXT, false); + DEBUG_SHUTDOWN = getBooleanOption(OPTION_DEBUG_SHUTDOWN, false); + DEBUG_REGISTRY = getBooleanOption(OPTION_DEBUG_REGISTRY, false); + DEBUG_REGISTRY_DUMP = getOption(OPTION_DEBUG_REGISTRY_DUMP); + DEBUG_PREFERENCES = getBooleanOption(OPTION_DEBUG_PREFERENCES, false); + } + } + /** + * Notifies all listeners of the platform log. This includes the console log, if + * used, and the platform log file. All Plugin log messages get funnelled + * through here as well. + */ + public void log(final IStatus status) { + assertInitialized(); + // create array to avoid concurrent access + ILogListener[] listeners; + synchronized (logListeners) { + listeners = (ILogListener[]) logListeners.toArray(new ILogListener[logListeners.size()]); + } + for (int i = 0; i < listeners.length; i++) { + final ILogListener listener = listeners[i]; + ISafeRunnable code = new ISafeRunnable() { + public void run() throws Exception { + listener.logging(status, PI_RUNTIME); + } + public void handleException(Throwable e) { + } + }; + run(code); + } + } + + private String[] processCommandLine(String[] args) { + if (args == null) + return args; + allArgs = args; + int[] configArgs = new int[100]; + //need to initialize the first element to something that could not be an index. + configArgs[0] = -1; + int configArgIndex = 0; + for (int i = 0; i < args.length; i++) { + boolean found = false; + // check for args without parameters (i.e., a flag arg) + + // look for the log flag + if (args[i].equalsIgnoreCase(LOG)) { + consoleLogEnabled = true; + found = true; + } + + // look for the no registry cache flag + if (args[i].equalsIgnoreCase(NOREGISTRYCACHE)) { + cacheRegistry = false; + found = true; + } + + // check to see if we should NOT be lazily loading plug-in definitions from the registry cache file. + // This will be processed below. + if (args[i].equalsIgnoreCase(NO_LAZY_REGISTRY_CACHE_LOADING)) { + lazyRegistryCacheLoading = false; + found = true; + } + + // look for the flag to turn off using package prefixes + if (args[i].equalsIgnoreCase(NO_PACKAGE_PREFIXES)) { + // ignored + // PluginClassLoader.usePackagePrefixes = false; + found = true; + } + + // look for the flag to turn off the workspace metadata version check + if (args[i].equalsIgnoreCase(NO_VERSION_CHECK)) { + doVersionCheck = false; + found = true; + } + + // this option (may have and argument) comes from InternalBootLoader.processCommandLine + if (args[i].equalsIgnoreCase(CLASSLOADER_PROPERTIES)) { + // ignored + found = true; + } + + // done checking for args. Remember where an arg was found + if (found) { + configArgs[configArgIndex++] = i; + continue; + } + // check for args with parameters + if (i == args.length - 1 || args[i + 1].startsWith("-")) //$NON-NLS-1$ + continue; + String arg = args[++i]; + + // look for the default data location + if (args[i - 1].equalsIgnoreCase(ARG_DATA)) { + location = new Path(arg); + found = true; + } + + // look for the keyring file + if (args[i - 1].equalsIgnoreCase(KEYRING)) { + keyringFile = arg; + found = true; + } + + // look for the user password. + if (args[i - 1].equalsIgnoreCase(PASSWORD)) { + password = arg; + found = true; + } + + // look for the application to run. + if (args[i - 1].equalsIgnoreCase(ARG_APPLICATION)) { + System.setProperty("eclipse.application", arg); //$NON-NLS-1$ + found = true; + } + + // look for the plug-in customization file + if (args[i - 1].equalsIgnoreCase(PLUGIN_CUSTOMIZATION)) { + pluginCustomizationFile = arg; + found = true; + } + + if (args[i - 1].equalsIgnoreCase(CLASSLOADER_PROPERTIES)) { + // ignored + found = true; + } + + // done checking for args. Remember where an arg was found + if (found) { + configArgs[configArgIndex++] = i - 1; + configArgs[configArgIndex++] = i; + } + } + + // remove all the arguments consumed by this argument parsing + if (configArgIndex == 0) { + appArgs = args; + return args; + } + appArgs = new String[args.length - configArgIndex]; + frameworkArgs = new String[configArgIndex]; + configArgIndex = 0; + int j = 0; + int k = 0; + for (int i = 0; i < args.length; i++) { + if (i == configArgs[configArgIndex]) { + frameworkArgs[k++] = args[i]; + configArgIndex++; + } else + appArgs[j++] = args[i]; + } + return appArgs; + } + + /** + * @see Platform#removeLogListener + */ + public void removeLogListener(ILogListener listener) { + assertInitialized(); + synchronized (logListeners) { + logListeners.remove(listener); + } + } + /** + * @see Platform + */ + public URL resolve(URL url) throws IOException { + if (!url.getProtocol().equals(PlatformURLHandler.PROTOCOL)) + return url; + URLConnection connection = url.openConnection(); + if (connection instanceof PlatformURLConnection) + return ((PlatformURLConnection) connection).getResolvedURL(); + else + return url; + } + public void run(ISafeRunnable code) { + Assert.isNotNull(code); + try { + code.run(); + } catch (Exception e) { + handleException(code, e); + } catch (LinkageError e) { + handleException(code, e); + } + } + private void run(Runnable handler) { + // run end-of-initialization handler + if (handler == null) + return; + + final Runnable finalHandler = handler; + ISafeRunnable code = new ISafeRunnable() { + public void run() throws Exception { + finalHandler.run(); + } + public void handleException(Throwable e) { + // just continue ... the exception has already been logged by + // the platform (see handleException(ISafeRunnable) + } + }; + run(code); + } + public void setOption(String option, String value) { + if (options != null) + options.setOption(option, value); + } + private void setupMetaArea() throws CoreException { + // if a platform location was not found in the arguments, compute one. + if (location == null) { + // Default location for the workspace is <user.dir>/workspace/ + location = new Path(System.getProperty("user.dir")).append(WORKSPACE); //$NON-NLS-1$ + } + if (!location.isAbsolute()) + location = new Path(System.getProperty("user.dir")).append(location); //$NON-NLS-1$ + // must create the meta area first as it defines all the other locations. + if (location.toFile().exists()) { + if (!location.toFile().isDirectory()) { + String message = Policy.bind("meta.notDir", location.toString()); //$NON-NLS-1$ + throw new CoreException(new Status(IStatus.ERROR, PI_RUNTIME, FAILED_WRITE_METADATA, message, null)); + } + } + //try infer the device if there isn't one (windows) + if (location.getDevice()==null) + location = new Path(location.toFile().getAbsolutePath()); + metaArea = new PlatformMetaArea(location); + metaArea.createLocation(); + if (keyringFile == null) + keyringFile = metaArea.getLocation().append(PlatformMetaArea.F_KEYRING).toOSString(); + } + + public void addLastModifiedTime(String pathKey, long lastModTime) { + if (regIndex == null) + regIndex = new HashMap(30); + regIndex.put(pathKey, new Long(lastModTime)); + } + public Map getRegIndex() { + return regIndex; + } + public void clearRegIndex() { + regIndex = null; + } + + /** + * Look for the companion preference translation file for a group + * of preferences. This method will attempt to find a companion + * ".properties" file first. This companion file can be in an + * nl-specific directory for this plugin or any of its fragments or + * it can be in the root of this plugin or the root of any of the + * plugin's fragments. This properties file can be used to translate + * preference values. + * + * @param pluginDescriptor the descriptor of the plugin + * who has the preferences + * @param basePrefFileName the base name of the preference file + * This base will be used to construct the name of the + * companion translation file. + * Example: If basePrefFileName is "plugin_customization", + * the preferences are in "plugin_customization.ini" and + * the translations are found in + * "plugin_customization.properties". + * @return the properties file + * + * @since 2.0 + */ + public Properties getPreferenceTranslator(String uniqueIdentifier, String basePrefFileName) { + return new Properties(); + } + + /** + * Takes a preference value and a related resource bundle and + * returns the translated version of this value (if one exists). + * + * @param value the preference value for potential translation + * @param bundle the bundle containing the translated values + * + * @since 2.0 + */ + public String translatePreference(String value, Properties props) { + value = value.trim(); + if (props == null || value.startsWith(KEY_DOUBLE_PREFIX)) + return value; + if (value.startsWith(KEY_PREFIX)) { + + int ix = value.indexOf(" "); //$NON-NLS-1$ + String key = ix == -1 ? value : value.substring(0, ix); + String dflt = ix == -1 ? value : value.substring(ix + 1); + return props.getProperty(key.substring(1), dflt); + } + return value; + } + + /** + * Applies primary feature-specific overrides to default preferences for the + * plug-in with the given id. + * <p> + * Note that by the time this method is called, the default settings + * for the plug-in itself should have already have been filled in. + * </p> + * + * @param id the unique identifier of the plug-in + * @param preferences the preference store for the specified plug-in + * + * @since 2.0 + */ + public void applyPrimaryFeaturePluginDefaultOverrides(String id, Preferences preferences) { + } + + /** + * Applies command line-supplied overrides to default preferences for the + * plug-in with the given id. + * <p> + * Note that by the time this method is called, the default settings + * for the plug-in itself should have already have been filled in, along + * with any default overrides supplied by the primary feature. + * </p> + * + * @param id the unique identifier of the plug-in + * @param preferences the preference store for the specified plug-in + * + * @since 2.0 + */ + public void applyCommandLinePluginDefaultOverrides(String id, Preferences preferences) { + + if (pluginCustomizationFile == null) { + // no command line overrides to process + if (DEBUG_PREFERENCES) { + System.out.println("Command line argument -pluginCustomization not used."); //$NON-NLS-1$ + } + return; + } + + try { + URL pluginCustomizationURL = new File(pluginCustomizationFile).toURL(); + if (DEBUG_PREFERENCES) { + System.out.println("Loading preferences from " + pluginCustomizationURL); //$NON-NLS-1$ + } + applyPluginDefaultOverrides(pluginCustomizationURL, id, preferences, null); + } catch (MalformedURLException e) { + // fail silently + if (DEBUG_PREFERENCES) { + System.out.println("MalformedURLException creating URL for plugin customization file " //$NON-NLS-1$ + +pluginCustomizationFile); + e.printStackTrace(); + } + return; + } + } + + /** + * Applies overrides to default preferences for the plug-in with the given id. + * The data is contained in the <code>java.io.Properties</code> style file at + * the given URL. The property names consist of "/'-separated plug-in id and + * name of preference; e.g., "com.example.myplugin/mypref". + * + * @param propertiesURL the URL of a <code>java.io.Properties</code> style file + * @param id the unique identifier of the plug-in + * @param preferences the preference store for the specified plug-in + * + * @since 2.0 + */ + private void applyPluginDefaultOverrides(URL propertiesURL, String id, Preferences preferences, Properties props) { + + // read the java.io.Properties file at the given URL + Properties overrides = new Properties(); + SafeFileInputStream in = null; + + try { + File inFile = new File(propertiesURL.getFile()); + if (!inFile.exists()) { + // We don't have a preferences file to worry about + if (DEBUG_PREFERENCES) { + System.out.println("Preference file " + //$NON-NLS-1$ + propertiesURL + " not found."); //$NON-NLS-1$ + } + return; + } + + in = new SafeFileInputStream(inFile); + if (in == null) { + // fail quietly + if (DEBUG_PREFERENCES) { + System.out.println("Failed to open " + //$NON-NLS-1$ + propertiesURL); + } + return; + } + overrides.load(in); + } catch (IOException e) { + // cannot read ini file - fail silently + if (DEBUG_PREFERENCES) { + System.out.println("IOException reading preference file " + //$NON-NLS-1$ + propertiesURL); + e.printStackTrace(); + } + return; + } finally { + try { + if (in != null) { + in.close(); + } + } catch (IOException e) { + // ignore problems closing file + if (DEBUG_PREFERENCES) { + System.out.println("IOException closing preference file " + //$NON-NLS-1$ + propertiesURL); + e.printStackTrace(); + } + } + } + + for (Iterator it = overrides.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + String qualifiedKey = (String) entry.getKey(); + // Keys consist of "/'-separated plug-in id and name of preference + // e.g., "com.example.myplugin/mypref" + int s = qualifiedKey.indexOf('/'); + if (s < 0 || s == 0 || s == qualifiedKey.length() - 1) { + // skip mangled entry + continue; + } + // plug-in id is non-empty string before "/" + String pluginId = qualifiedKey.substring(0, s); + if (pluginId.equals(id)) { + // override property in the given plug-in + // plig-in-specified property name is non-empty string after "/" + String propertyName = qualifiedKey.substring(s + 1); + String value = (String) entry.getValue(); + value = translatePreference(value, props); + preferences.setDefault(propertyName, value); + } + } + if (DEBUG_PREFERENCES) { + System.out.println("Preferences now set as follows:"); //$NON-NLS-1$ + String[] prefNames = preferences.propertyNames(); + for (int i = 0; i < prefNames.length; i++) { + String value = preferences.getString(prefNames[i]); + System.out.println("\t" + prefNames[i] + " = " + value); //$NON-NLS-1$ //$NON-NLS-2$ + } + prefNames = preferences.defaultPropertyNames(); + for (int i = 0; i < prefNames.length; i++) { + String value = preferences.getDefaultString(prefNames[i]); + System.out.println("\tDefault values: " + prefNames[i] + " = " + value); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + } + public void setExtensionRegistry(IExtensionRegistry value) { + registry = value; + } + public BundleContext getBundleContext() { + return context; + } + public Bundle getBundle(String id) { + return getBundleContext().getBundle(id); + } + + public URL getInstallURL() { + if (installLocation == null) + try { + installLocation = new URL((String) System.getProperty("eclipse.installURL")); //$NON-NLS-1$ + } catch (MalformedURLException e) { + //This can't fail because eclipse.installURL has been set with a valid URL in BootLoader + } + return installLocation; + } + public EnvironmentInfo getEnvironmentInfoService() { + return infoService; + } + /** + * @deprecated either look for the IPlatform service or see if the runtime bundle is started. + */ + public boolean isRunning() { + int state = context.getBundle(PI_RUNTIME).getState(); + return state == Bundle.ACTIVE; + } + + /* + * This method is retained for R1.0 compatibility because it is defined as API. + * It's function matches the API description (returns <code>null</code> when + * argument URL is <code>null</code> or cannot be read). + */ + public URL[] getPluginPath(URL pluginPathLocation /*R1.0 compatibility*/ + ) { + InputStream input = null; + // first try and see if the given plugin path location exists. + if (pluginPathLocation == null) + return null; + try { + input = pluginPathLocation.openStream(); + } catch (IOException e) { + //fall through + } + + // if the given path was null or did not exist, look for a plugin path + // definition in the install location. + if (input == null) + try { + URL url = new URL(PlatformURLBaseConnection.PLATFORM_URL_STRING + PLUGIN_PATH); + input = url.openStream(); + } catch (MalformedURLException e) { + //fall through + } catch (IOException e) { + //fall through + } + + // nothing was found at the supplied location or in the install location + if (input == null) + return null; + // if we found a plugin path definition somewhere so read it and close the location. + URL[] result = null; + try { + try { + result = readPluginPath(input); + } finally { + input.close(); + } + } catch (IOException e) { + //let it return null on failure to read + } + return result; + } + + private URL[] readPluginPath(InputStream input) { + Properties ini = new Properties(); + try { + ini.load(input); + } catch (IOException e) { + return null; + } + Vector result = new Vector(5); + for (Enumeration groups = ini.propertyNames(); groups.hasMoreElements();) { + String group = (String) groups.nextElement(); + for (StringTokenizer entries = new StringTokenizer(ini.getProperty(group), ";"); entries.hasMoreElements();) { //$NON-NLS-1$ + String entry = (String) entries.nextElement(); + if (!entry.equals("")) //$NON-NLS-1$ + try { + result.addElement(new URL(entry)); + } catch (MalformedURLException e) { + //intentionally ignore bad URLs + System.err.println(Policy.bind("ignore.plugin", entry)); //$NON-NLS-1$ + } + } + } + return (URL[]) result.toArray(new URL[result.size()]); + } + + /* (non-Javadoc) + * @see org.eclipse.core.runtime.IPlatform#getConfigurationMetadataLocation() + */ + public IPath getConfigurationMetadataLocation() { + if (configMetadataLocation == null) + configMetadataLocation = new Path(System.getProperty("osgi.configuration.area")); //$NON-NLS-1$ + return configMetadataLocation; + } + + public IPath getStateLocation(Bundle bundle, boolean create) { + assertInitialized(); + IPath result = metaArea.getStateLocation(bundle); + if (create) + result.toFile().mkdirs(); + return result; + } + public URL find(Bundle b, IPath path) { + return FindSupport.find(b, path); + } + public URL find(Bundle bundle, IPath path, Map override) { + return FindSupport.find(bundle, path, override); + } + public InputStream openStream(Bundle bundle, IPath file) throws IOException { + return FindSupport.openStream(bundle, file, false); + } + public InputStream openStream(Bundle bundle, IPath file, boolean localized) throws IOException { + return FindSupport.openStream(bundle, file, localized); + } + public IPath getStateLocation(Bundle bundle) { + return getStateLocation(bundle, true); + } + public ResourceBundle getResourceBundle(Bundle bundle) throws MissingResourceException { + //TODO + throw new NoSuchMethodError("getResourceBundleString"); + } + public String getResourceString(Bundle bundle, String value) { + //TODO + throw new NoSuchMethodError("getResourceBundleString"); + } + public String getResourceString(Bundle bundle, String value, ResourceBundle resourceBundle) { + //TODO + throw new NoSuchMethodError("getResourceBundleString"); + } +}
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/PlatformActivator.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/PlatformActivator.java index 3bb9b9b..583cc47 100644 --- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/PlatformActivator.java +++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/PlatformActivator.java
@@ -1,217 +1,216 @@ -/******************************************************************************* - * Copyright (c) 2000, 2003 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Common Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/cpl-v10.html - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.core.internal.runtime; - -import java.io.File; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Hashtable; -import org.eclipse.core.internal.boot.PlatformURLBaseConnection; -import org.eclipse.core.internal.boot.PlatformURLHandler; -import org.eclipse.core.internal.registry.*; -import org.eclipse.core.runtime.*; -import org.eclipse.osgi.service.environment.EnvironmentInfo; -import org.osgi.framework.*; -import org.osgi.service.url.URLConstants; -import org.osgi.service.url.URLStreamHandlerService; - -/** - * Activator for the Eclipse runtime. - */ -public class PlatformActivator implements BundleActivator, ServiceListener { - - private static BundleContext context; - private EclipseBundleListener pluginBundleListener; - private ExtensionRegistry registry; - private ServiceReference environmentServiceReference; - private ServiceRegistration converterRegistration; - - private static File cacheFile = InternalPlatform.getDefault().getConfigurationMetadataLocation().append(".registry").toFile(); - - public static BundleContext getContext() { - return context; - } - - public void start(BundleContext context) throws Exception { - PlatformActivator.context = context; - context.addServiceListener(this); - tryToAcquireInfoService(); - installPlatformURLSupport(); - } - - private void installBackwardCompatibleURLSupport() { - try { - Class handler = Class.forName("org.eclipse.core.internal.runtime.PlatformURLPluginHandlerFactory"); - Method startupMethod = handler.getDeclaredMethod("startup", null); - startupMethod.invoke(handler, null); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - return; //TODO log a warning - } catch (SecurityException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (NoSuchMethodException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalArgumentException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalAccessException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (InvocationTargetException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - /** - * Register the platform URL support as a service to the URLHandler service - */ - private void installPlatformURLSupport() { - PlatformURLPluginConnection.startup(); - PlatformURLFragmentConnection.startup(); - - PlatformURLBaseConnection.startup(InternalPlatform.getDefault().getInstallURL()); - - Hashtable properties = new Hashtable(); - properties.put(URLConstants.URL_HANDLER_PROTOCOL, new String[] { PlatformURLHandler.PROTOCOL }); - context.registerService(URLStreamHandlerService.class.getName(), new PlatformURLHandler(), properties); - } - - private void startRegistry(BundleContext context) { - boolean fromCache = true; - if (InternalPlatform.cacheRegistry) { - // Try to read the registry from the cache first. If that fails, create a new registry - MultiStatus problems = new MultiStatus(IPlatform.PI_RUNTIME, ExtensionsParser.PARSE_PROBLEM, "Registry cache problems", null); //$NON-NLS-1$ - Factory factory = new Factory(problems); - - long start = 0; - if (InternalPlatform.DEBUG) - start = System.currentTimeMillis(); - registry = new RegistryCacheReader(cacheFile, factory, InternalPlatform.lazyRegistryCacheLoading).loadCache(); - - if (InternalPlatform.DEBUG && registry != null) - System.out.println("Reading registry cache: " + (System.currentTimeMillis() - start)); - - if (InternalPlatform.DEBUG_REGISTRY) { - if (registry == null) - System.out.println("Reloading registry from manifest files..."); - else - System.out.println("Using registry cache " + (InternalPlatform.lazyRegistryCacheLoading ? "with" : "without") + " lazy element loading..."); - } - // TODO log any problems that occurred in loading the cache. - if (!problems.isOK()) - System.out.println(problems); - } - if (registry == null) { - fromCache = false; - registry = new ExtensionRegistry(new ExtensionLinker()); - } - - // register a listener to catch new bundle installations/resolutions. - pluginBundleListener = new EclipseBundleListener(registry); - context.addBundleListener(pluginBundleListener); - - // populate the registry with all the currently installed bundles. - if (!fromCache) - pluginBundleListener.processBundles(context.getBundles()); - - context.registerService(IExtensionRegistry.class.getName(), registry, new Hashtable()); //$NON-NLS-1$ - InternalPlatform.getDefault().setExtensionRegistry(registry); - } - public void stop(BundleContext context) throws Exception { - // Stop the registry - stopRegistry(context); - unregisterPluginConverter(); - environmentInfoServiceReleased(environmentServiceReference); - // Stop the platform orderly. - InternalPlatform.getDefault().stop(context); - } - - - private void stopRegistry(BundleContext context) { - context.removeBundleListener(this.pluginBundleListener); - if (registry != null && registry.isDirty()) { - new RegistryCacheWriter(cacheFile).saveCache(registry); - registry = null; - } - } - - private void tryToAcquireInfoService() { - ServiceReference reference = context.getServiceReference(EnvironmentInfo.class.getName()); - if (reference == null) - return; - environmentInfoServiceAquired(reference); - } - - private void environmentInfoServiceAquired(ServiceReference reference) { - if (environmentServiceReference != null) - return; - environmentServiceReference = reference; - EnvironmentInfo infoService = (EnvironmentInfo) context.getService(environmentServiceReference); - InternalPlatform.infoService = infoService; - toStart(); - } - - private void toStart() { - try { - InternalPlatform.getDefault().start(context); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - registerPluginConverter(); - startRegistry(context); - - } - - private void registerPluginConverter() { - converterRegistration = context.registerService(IPluginConverter.class.getName(), new PluginConverter(), null); - } - - private void unregisterPluginConverter() { - converterRegistration.unregister(); - } - - private void environmentInfoServiceReleased(ServiceReference reference) { - if (environmentServiceReference == null) - return; - if (environmentServiceReference != reference) - return; - - InternalPlatform.infoService = null; - context.ungetService(environmentServiceReference); - environmentServiceReference = null; - } - - public void serviceChanged(ServiceEvent event) { - int type = event.getType(); - ServiceReference reference = event.getServiceReference(); - switch (type) { - case ServiceEvent.REGISTERED : - String[] servicesInterfaces = (String[]) reference.getProperty(Constants.OBJECTCLASS); - for (int i = 0; i < servicesInterfaces.length; i++) { - if (servicesInterfaces[i].equals(EnvironmentInfo.class.getName())) - environmentInfoServiceAquired(reference); - } - break; - case ServiceEvent.UNREGISTERING : - System.out.println("Service Unregistering:"); - servicesInterfaces = (String[]) reference.getProperty(Constants.OBJECTCLASS); - for (int i = 0; i < servicesInterfaces.length; i++) { - if (servicesInterfaces[i].equals(EnvironmentInfo.class.getName())) - environmentInfoServiceReleased(reference); - } - break; - } - } -} +/******************************************************************************* + * Copyright (c) 2000, 2003 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.runtime; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Hashtable; +import org.eclipse.core.internal.boot.PlatformURLBaseConnection; +import org.eclipse.core.internal.boot.PlatformURLHandler; +import org.eclipse.core.internal.registry.*; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.service.environment.EnvironmentInfo; +import org.osgi.framework.*; +import org.osgi.service.url.URLConstants; +import org.osgi.service.url.URLStreamHandlerService; + +/** + * Activator for the Eclipse runtime. + */ +public class PlatformActivator implements BundleActivator, ServiceListener { + + private static BundleContext context; + private EclipseBundleListener pluginBundleListener; + private ExtensionRegistry registry; + private ServiceReference environmentServiceReference; + private ServiceRegistration converterRegistration; + + private static File cacheFile = InternalPlatform.getDefault().getConfigurationMetadataLocation().append(".registry").toFile(); + + public static BundleContext getContext() { + return context; + } + + public void start(BundleContext context) throws Exception { + PlatformActivator.context = context; + context.addServiceListener(this); + tryToAcquireInfoService(); + installPlatformURLSupport(); + } + + private void installBackwardCompatibleURLSupport() { + try { + Class handler = Class.forName("org.eclipse.core.internal.runtime.PlatformURLPluginHandlerFactory"); + Method startupMethod = handler.getDeclaredMethod("startup", null); + startupMethod.invoke(handler, null); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + return; //TODO log a warning + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchMethodException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + /** + * Register the platform URL support as a service to the URLHandler service + */ + private void installPlatformURLSupport() { + PlatformURLPluginConnection.startup(); + PlatformURLFragmentConnection.startup(); + + PlatformURLBaseConnection.startup(InternalPlatform.getDefault().getInstallURL()); + + Hashtable properties = new Hashtable(); + properties.put(URLConstants.URL_HANDLER_PROTOCOL, new String[] { PlatformURLHandler.PROTOCOL }); + context.registerService(URLStreamHandlerService.class.getName(), new PlatformURLHandler(), properties); + } + + private void startRegistry(BundleContext context) { + boolean fromCache = true; + if (InternalPlatform.cacheRegistry) { + // Try to read the registry from the cache first. If that fails, create a new registry + MultiStatus problems = new MultiStatus(IPlatform.PI_RUNTIME, ExtensionsParser.PARSE_PROBLEM, "Registry cache problems", null); //$NON-NLS-1$ + Factory factory = new Factory(problems); + + long start = 0; + if (InternalPlatform.DEBUG) + start = System.currentTimeMillis(); + registry = new RegistryCacheReader(cacheFile, factory, InternalPlatform.lazyRegistryCacheLoading).loadCache(); + + if (InternalPlatform.DEBUG && registry != null) + System.out.println("Reading registry cache: " + (System.currentTimeMillis() - start)); + + if (InternalPlatform.DEBUG_REGISTRY) { + if (registry == null) + System.out.println("Reloading registry from manifest files..."); + else + System.out.println("Using registry cache " + (InternalPlatform.lazyRegistryCacheLoading ? "with" : "without") + " lazy element loading..."); + } + // TODO log any problems that occurred in loading the cache. + if (!problems.isOK()) + System.out.println(problems); + } + if (registry == null) { + fromCache = false; + registry = new ExtensionRegistry(new ExtensionLinker()); + } + + // register a listener to catch new bundle installations/resolutions. + pluginBundleListener = new EclipseBundleListener(registry); + context.addBundleListener(pluginBundleListener); + + // populate the registry with all the currently installed bundles. + if (!fromCache) + pluginBundleListener.processBundles(context.getBundles()); + + context.registerService(IExtensionRegistry.class.getName(), registry, new Hashtable()); //$NON-NLS-1$ + InternalPlatform.getDefault().setExtensionRegistry(registry); + } + public void stop(BundleContext context) throws Exception { + // Stop the registry + stopRegistry(context); + unregisterPluginConverter(); + environmentInfoServiceReleased(environmentServiceReference); + // Stop the platform orderly. + InternalPlatform.getDefault().stop(context); + } + + + private void stopRegistry(BundleContext context) { + context.removeBundleListener(this.pluginBundleListener); + if (registry != null && registry.isDirty()) { + new RegistryCacheWriter(cacheFile).saveCache(registry); + registry = null; + } + } + + private void tryToAcquireInfoService() { + ServiceReference reference = context.getServiceReference(EnvironmentInfo.class.getName()); + if (reference == null) + return; + environmentInfoServiceAquired(reference); + } + + private void environmentInfoServiceAquired(ServiceReference reference) { + if (environmentServiceReference != null) + return; + environmentServiceReference = reference; + EnvironmentInfo infoService = (EnvironmentInfo) context.getService(environmentServiceReference); + InternalPlatform.infoService = infoService; + toStart(); + } + + private void toStart() { + try { + InternalPlatform.getDefault().start(context); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + registerPluginConverter(); + startRegistry(context); + + } + + private void registerPluginConverter() { + converterRegistration = context.registerService(IPluginConverter.class.getName(), new PluginConverter(), null); + } + + private void unregisterPluginConverter() { + converterRegistration.unregister(); + } + + private void environmentInfoServiceReleased(ServiceReference reference) { + if (environmentServiceReference == null) + return; + if (environmentServiceReference != reference) + return; + + InternalPlatform.infoService = null; + context.ungetService(environmentServiceReference); + environmentServiceReference = null; + } + + public void serviceChanged(ServiceEvent event) { + int type = event.getType(); + ServiceReference reference = event.getServiceReference(); + switch (type) { + case ServiceEvent.REGISTERED : + String[] servicesInterfaces = (String[]) reference.getProperty(Constants.OBJECTCLASS); + for (int i = 0; i < servicesInterfaces.length; i++) { + if (servicesInterfaces[i].equals(EnvironmentInfo.class.getName())) + environmentInfoServiceAquired(reference); + } + break; + case ServiceEvent.UNREGISTERING : + servicesInterfaces = (String[]) reference.getProperty(Constants.OBJECTCLASS); + for (int i = 0; i < servicesInterfaces.length; i++) { + if (servicesInterfaces[i].equals(EnvironmentInfo.class.getName())) + environmentInfoServiceReleased(reference); + } + break; + } + } +}
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/IPlatform.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/IPlatform.java index 5217fe0..617ca3f 100644 --- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/IPlatform.java +++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/IPlatform.java
@@ -1,439 +1,438 @@ -/******************************************************************************* - * Copyright (c) 2000, 2003 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Common Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/cpl-v10.html - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.core.runtime; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.*; -import org.eclipse.core.runtime.jobs.IJobManager; -import org.osgi.framework.Bundle; - -// TODO clarify the javadoc below. Copy the signatures from Platform. -// talk to jeem about the best way to do the triplication -/** - * The central class of the Eclipse Platform Runtime. This class cannot - * be instantiated or subclassed by clients; all functionality is provided - * by static methods. Features include: - * <ul> - * <li>the platform registry of installed plug-ins</li> - * <li>the platform adapter manager</li> - * <li>the platform log</li> - * <li>the authorization info management</li> - * </ul> - * <p> - * The platform is in one of two states, running or not running, at all - * times. The only ways to start the platform running, or to shut it down, - * are on the bootstrap <code>BootLoader</code> class. Code in plug-ins will - * only observe the platform in the running state. The platform cannot - * be shutdown from inside (code in plug-ins have no access to - * <code>BootLoader</code>). - * </p> - * @deprecated Use Platform instead. - */ -public interface IPlatform { - /** - * Name of a preference for configuring the performance level for this system. - * - * <p> - * This value can be used by all components to customize features to suit the - * speed of the user's machine. The platform job manager uses this value to make - * scheduling decisions about background jobs. - * </p> - * <p> - * The preference value must be an integer between the constant values - * MIN_PERFORMANCE and MAX_PERFORMANCE - * </p> - * @see #MIN_PERFORMANCE - * @see #MAX_PERFORMANCE - * @since 3.0 - */ - public static final String PREF_PLATFORM_PERFORMANCE = "runtime.performance"; //$NON-NLS-1$ - /** - * The unique identifier constant (value "<code>org.eclipse.core.runtime</code>") - * of the Core Runtime plug-in. - */ - public static final String PI_RUNTIME = "org.eclipse.core.runtime"; //$NON-NLS-1$ - public static final String PI_RUNTIME_COMPATIBILITY = "org.eclipse.core.runtime.compatibility"; //$NON-NLS-1$ - /** - * The simple identifier constant (value "<code>applications</code>") of - * the extension point of the Core Runtime plug-in where plug-ins declare - * the existence of runnable applications. A plug-in may define any - * number of applications; however, the platform is only capable - * of running one application at a time. - * - * @see org.eclipse.core.boot.BootLoader#run - */ - public static final String PT_APPLICATIONS = "applications"; //$NON-NLS-1$ - - public static final String PT_URLHANDLERS = "urlHandlers"; //$NON-NLS-1$ - - public static final String PT_SHUTDOWN_HOOK = "applicationShutdownHook"; //$NON-NLS-1$ - - /** - * Status code constant (value 1) indicating a problem in a plug-in - * manifest (<code>plugin.xml</code>) file. - */ - public static final int PARSE_PROBLEM = 1; - - /** - * Status code constant (value 2) indicating an error occurred while running a plug-in. - */ - public static final int PLUGIN_ERROR = 2; - - /** - * Status code constant (value 3) indicating an error internal to the - * platform has occurred. - */ - public static final int INTERNAL_ERROR = 3; - - /** - * Status code constant (value 4) indicating the platform could not read - * some of its metadata. - */ - public static final int FAILED_READ_METADATA = 4; - - /** - * Status code constant (value 5) indicating the platform could not write - * some of its metadata. - */ - public static final int FAILED_WRITE_METADATA = 5; - - /** - * Status code constant (value 6) indicating the platform could not delete - * some of its metadata. - */ - public static final int FAILED_DELETE_METADATA = 6; - - /** - * Adds the given authorization information to the keyring. The - * information is relevant for the specified protection space and the - * given authorization scheme. The protection space is defined by the - * combination of the given server URL and realm. The authorization - * scheme determines what the authorization information contains and how - * it should be used. The authorization information is a <code>Map</code> - * of <code>String</code> to <code>String</code> and typically - * contains information such as usernames and passwords. - * - * @param serverUrl the URL identifying the server for this authorization - * information. For example, "http://www.example.com/". - * @param realm the subsection of the given server to which this - * authorization information applies. For example, - * "realm1@example.com" or "" for no realm. - * @param authScheme the scheme for which this authorization information - * applies. For example, "Basic" or "" for no authorization scheme - * @param info a <code>Map</code> containing authorization information - * such as usernames and passwords (key type : <code>String</code>, - * value type : <code>String</code>) - * @exception CoreException if there are problems setting the - * authorization information. Reasons include: - * <ul> - * <li>The keyring could not be saved.</li> - * </ul> - */ - public void addAuthorizationInfo(URL serverUrl, String realm, String authScheme, Map info) throws CoreException; - - /** - * Adds the given log listener to the notification list of the platform. - * <p> - * Once registered, a listener starts receiving notification as entries - * are added to plug-in logs via <code>ILog.log()</code>. The listener continues to - * receive notifications until it is replaced or removed. - * </p> - * - * @param listener the listener to register - * @see ILog#addLogListener - * @see #removeLogListener - */ - public void addLogListener(ILogListener listener); - - /** - * Adds the specified resource to the protection space specified by the - * given realm. All targets at or deeper than the depth of the last - * symbolic element in the path of the given resource URL are assumed to - * be in the same protection space. - * - * @param resourceUrl the URL identifying the resources to be added to - * the specified protection space. For example, - * "http://www.example.com/folder/". - * @param realm the name of the protection space. For example, - * "realm1@example.com" - * @exception CoreException if there are problems setting the - * authorization information. Reasons include: - * <ul> - * <li>The keyring could not be saved.</li> - * </ul> - */ - public void addProtectionSpace(URL resourceUrl, String realm) throws CoreException; - - /** - * Returns a URL which is the local equivalent of the - * supplied URL. This method is expected to be used with - * plug-in-relative URLs returned by IPluginDescriptor. - * If the specified URL is not a plug-in-relative URL, it - * is returned asis. If the specified URL is a plug-in-relative - * URL of a file (incl. .jar archive), it is returned as - * a locally-accessible URL using "file:" or "jar:file:" protocol - * (caching the file locally, if required). If the specified URL - * is a plug-in-relative URL of a directory, - * an exception is thrown. - * - * @param url original plug-in-relative URL. - * @return the resolved URL - * @exception IOException if unable to resolve URL - * @see #resolve - * @see IPluginDescriptor#getInstallURL - */ - public URL asLocalURL(URL url) throws IOException; - - /** - * Removes the authorization information for the specified protection - * space and given authorization scheme. The protection space is defined - * by the given server URL and realm. - * - * @param serverUrl the URL identifying the server to remove the - * authorization information for. For example, - * "http://www.example.com/". - * @param realm the subsection of the given server to remove the - * authorization information for. For example, - * "realm1@example.com" or "" for no realm. - * @param authScheme the scheme for which the authorization information - * to remove applies. For example, "Basic" or "" for no - * authorization scheme. - * @exception CoreException if there are problems removing the - * authorization information. Reasons include: - * <ul> - * <li>The keyring could not be saved.</li> - * </ul> - */ - public void flushAuthorizationInfo(URL serverUrl, String realm, String authScheme) throws CoreException; - - /** - * Returns the adapter manager used for extending - * <code>IAdaptable</code> objects. - * - * @return the adapter manager for this platform - * @see IAdapterManager - */ - public IAdapterManager getAdapterManager(); - - /** - * Returns the authorization information for the specified protection - * space and given authorization scheme. The protection space is defined - * by the given server URL and realm. Returns <code>null</code> if no - * such information exists. - * - * @param serverUrl the URL identifying the server for the authorization - * information. For example, "http://www.example.com/". - * @param realm the subsection of the given server to which the - * authorization information applies. For example, - * "realm1@example.com" or "" for no realm. - * @param authScheme the scheme for which the authorization information - * applies. For example, "Basic" or "" for no authorization scheme - * @return the authorization information for the specified protection - * space and given authorization scheme, or <code>null</code> if no - * such information exists - */ - public Map getAuthorizationInfo(URL serverUrl, String realm, String authScheme); - - /** - * Returns the location of the platform working directory. This - * corresponds to the <i>-data</i> command line argument if - * present or, if not, the current working directory when the platform - * was started. - * - * @return the location of the platform - */ - public IPath getLocation(); - - /** - * Returns the location of the platform log file. This file may contain information - * about errors that have previously occurred during this invocation of the Platform. - * - * Note: it is very important that users of this method do not leave the log - * file open for extended periods of time. Doing so may prevent others - * from writing to the log file, which could result in important error messages - * being lost. It is strongly recommended that clients wanting to read the - * log file for extended periods should copy the log file contents elsewhere, - * and immediately close the original file. - * - * @return the path of the log file on disk. - */ - public IPath getLogFileLocation(); - - /** - * Returns the protection space (realm) for the specified resource, or - * <code>null</code> if the realm is unknown. - * - * @param resourceUrl the URL of the resource whose protection space is - * returned. For example, "http://www.example.com/folder/". - * @return the protection space (realm) for the specified resource, or - * <code>null</code> if the realm is unknown - */ - public String getProtectionSpace(URL resourceUrl); - - /** - * Removes the indicated (identical) log listener from the notification list - * of the platform. If no such listener exists, no action is taken. - * - * @param listener the listener to deregister - * @see ILog#removeLogListener - * @see #addLogListener - */ - public void removeLogListener(ILogListener listener); - - /** - * Returns a URL which is the resolved equivalent of the - * supplied URL. This method is expected to be used with - * plug-in-relative URLs returned by IPluginDescriptor. - * If the specified URL is not a plug-in-relative URL, it is returned - * as is. If the specified URL is a plug-in-relative URL, it is - * resolved to a URL using the actual URL protocol - * (eg. file, http, etc) - * - * @param url original plug-in-relative URL. - * @return the resolved URL - * @exception IOException if unable to resolve URL - * @see #asLocalURL - * @see IPluginDescriptor#getInstallURL - */ - public URL resolve(URL url) throws IOException; - - /** - * Runs the given runnable in a protected mode. Exceptions - * thrown in the runnable are logged and passed to the runnable's - * exception handler. Such exceptions are not rethrown by this method. - * - * @param code the runnable to run - */ - public void run(ISafeRunnable code); - /** - * Returns the log for the given bundle. If no such log exists, one is created. - * - * @return the log for the given bundle - */ - public ILog getLog(Bundle bundle); - - /** - * Returns the platform job manager. - * - * @return the job manager - */ - public IJobManager getJobManager(); - - /** - * Returns URL at which the Platform runtime executables and libraries are installed. - * The returned value is distinct from the location of any given platform's data. - * - * @return the URL indicating where the platform runtime is installed. - */ - public URL getInstallURL(); - - /** - * Returns the location in the filesystem of the configuration information - * used to run this instance of Eclipse. The configuration area typically - * contains the list of plug-ins available for use, various user setttings - * (those shared across different instances of the same configuration) - * and any other such data needed by plug-ins. - * - * @return the path indicating the directory containing the configuration - * metadata for this running Eclipse. - */ - public IPath getConfigurationMetadataLocation(); - - /** - * Takes down the splash screen if one was put up. - */ - public void endSplash(); - - public URL find(Bundle b, IPath path); - - public URL find(Bundle b, IPath path, Map override); - - public InputStream openStream(Bundle b, IPath file) throws IOException; - /** - * Returns an input stream for the specified file. The file path - * must be specified relative to this plug-in's installation location. - * Optionally, the platform searches for the correct localized version - * of the specified file using the users current locale, and Java - * naming convention for localized resource files (locale suffix appended - * to the specified file extension). - * <p> - * The caller must close the returned stream when done. - * </p> - * - * @param file path relative to plug-in installation location - * @param localized <code>true</code> for the localized version - * of the file, and <code>false</code> for the file exactly - * as specified - * @return an input stream - */ - public InputStream openStream(Bundle b, IPath file, boolean localized) throws IOException; - - public IPath getStateLocation(Bundle bundle); - - public ResourceBundle getResourceBundle(Bundle bundle) throws MissingResourceException; - /** - * Returns a resource string corresponding to the given argument value. - * If the argument value specifies a resource key, the string - * is looked up in the default resource bundle. If the argument does not - * specify a valid key, the argument itself is returned as the - * resource string. The key lookup is performed in the - * plugin.properties resource bundle. If a resource string - * corresponding to the key is not found in the resource bundle - * the key value, or any default text following the key in the - * argument value is returned as the resource string. - * A key is identified as a string begining with the "%" character. - * Note, that the "%" character is stripped off prior to lookup - * in the resource bundle. - * <p> - * Equivalent to <code>getResourceString(value, getResourceBundle())</code> - * </p> - * - * @param value the value - * @return the resource string - * @see #getResourceBundle - */ - public String getResourceString(Bundle bundle, String value); - /** - * Returns a resource string corresponding to the given argument - * value and bundle. - * If the argument value specifies a resource key, the string - * is looked up in the given resource bundle. If the argument does not - * specify a valid key, the argument itself is returned as the - * resource string. The key lookup is performed against the - * specified resource bundle. If a resource string - * corresponding to the key is not found in the resource bundle - * the key value, or any default text following the key in the - * argument value is returned as the resource string. - * A key is identified as a string begining with the "%" character. - * Note that the "%" character is stripped off prior to lookup - * in the resource bundle. - * <p> - * For example, assume resource bundle plugin.properties contains - * name = Project Name - * <pre> - * getResourceString("Hello World") returns "Hello World"</li> - * getResourceString("%name") returns "Project Name"</li> - * getResourceString("%name Hello World") returns "Project Name"</li> - * getResourceString("%abcd Hello World") returns "Hello World"</li> - * getResourceString("%abcd") returns "%abcd"</li> - * getResourceString("%%name") returns "%name"</li> - * </pre> - * </p> - * - * @param value the value - * @param bundle the resource bundle - * @return the resource string - * @see #getResourceBundle - */ - public String getResourceString(Bundle bundle, String value, ResourceBundle resourceBundle); -} +/******************************************************************************* + * Copyright (c) 2000, 2003 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.runtime; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.*; +import org.eclipse.core.runtime.jobs.IJobManager; +import org.osgi.framework.Bundle; + +// TODO clarify the javadoc below. Copy the signatures from Platform. +// talk to jeem about the best way to do the triplication +/** + * The central class of the Eclipse Platform Runtime. This class cannot + * be instantiated or subclassed by clients; all functionality is provided + * by static methods. Features include: + * <ul> + * <li>the platform registry of installed plug-ins</li> + * <li>the platform adapter manager</li> + * <li>the platform log</li> + * <li>the authorization info management</li> + * </ul> + * <p> + * The platform is in one of two states, running or not running, at all + * times. The only ways to start the platform running, or to shut it down, + * are on the bootstrap <code>BootLoader</code> class. Code in plug-ins will + * only observe the platform in the running state. The platform cannot + * be shutdown from inside (code in plug-ins have no access to + * <code>BootLoader</code>). + * </p> + */ +public interface IPlatform { + /** + * Name of a preference for configuring the performance level for this system. + * + * <p> + * This value can be used by all components to customize features to suit the + * speed of the user's machine. The platform job manager uses this value to make + * scheduling decisions about background jobs. + * </p> + * <p> + * The preference value must be an integer between the constant values + * MIN_PERFORMANCE and MAX_PERFORMANCE + * </p> + * @see #MIN_PERFORMANCE + * @see #MAX_PERFORMANCE + * @since 3.0 + */ + public static final String PREF_PLATFORM_PERFORMANCE = "runtime.performance"; //$NON-NLS-1$ + /** + * The unique identifier constant (value "<code>org.eclipse.core.runtime</code>") + * of the Core Runtime plug-in. + */ + public static final String PI_RUNTIME = "org.eclipse.core.runtime"; //$NON-NLS-1$ + public static final String PI_RUNTIME_COMPATIBILITY = "org.eclipse.core.runtime.compatibility"; //$NON-NLS-1$ + /** + * The simple identifier constant (value "<code>applications</code>") of + * the extension point of the Core Runtime plug-in where plug-ins declare + * the existence of runnable applications. A plug-in may define any + * number of applications; however, the platform is only capable + * of running one application at a time. + * + * @see org.eclipse.core.boot.BootLoader#run + */ + public static final String PT_APPLICATIONS = "applications"; //$NON-NLS-1$ + + public static final String PT_URLHANDLERS = "urlHandlers"; //$NON-NLS-1$ + + public static final String PT_SHUTDOWN_HOOK = "applicationShutdownHook"; //$NON-NLS-1$ + + /** + * Status code constant (value 1) indicating a problem in a plug-in + * manifest (<code>plugin.xml</code>) file. + */ + public static final int PARSE_PROBLEM = 1; + + /** + * Status code constant (value 2) indicating an error occurred while running a plug-in. + */ + public static final int PLUGIN_ERROR = 2; + + /** + * Status code constant (value 3) indicating an error internal to the + * platform has occurred. + */ + public static final int INTERNAL_ERROR = 3; + + /** + * Status code constant (value 4) indicating the platform could not read + * some of its metadata. + */ + public static final int FAILED_READ_METADATA = 4; + + /** + * Status code constant (value 5) indicating the platform could not write + * some of its metadata. + */ + public static final int FAILED_WRITE_METADATA = 5; + + /** + * Status code constant (value 6) indicating the platform could not delete + * some of its metadata. + */ + public static final int FAILED_DELETE_METADATA = 6; + + /** + * Adds the given authorization information to the keyring. The + * information is relevant for the specified protection space and the + * given authorization scheme. The protection space is defined by the + * combination of the given server URL and realm. The authorization + * scheme determines what the authorization information contains and how + * it should be used. The authorization information is a <code>Map</code> + * of <code>String</code> to <code>String</code> and typically + * contains information such as usernames and passwords. + * + * @param serverUrl the URL identifying the server for this authorization + * information. For example, "http://www.example.com/". + * @param realm the subsection of the given server to which this + * authorization information applies. For example, + * "realm1@example.com" or "" for no realm. + * @param authScheme the scheme for which this authorization information + * applies. For example, "Basic" or "" for no authorization scheme + * @param info a <code>Map</code> containing authorization information + * such as usernames and passwords (key type : <code>String</code>, + * value type : <code>String</code>) + * @exception CoreException if there are problems setting the + * authorization information. Reasons include: + * <ul> + * <li>The keyring could not be saved.</li> + * </ul> + */ + public void addAuthorizationInfo(URL serverUrl, String realm, String authScheme, Map info) throws CoreException; + + /** + * Adds the given log listener to the notification list of the platform. + * <p> + * Once registered, a listener starts receiving notification as entries + * are added to plug-in logs via <code>ILog.log()</code>. The listener continues to + * receive notifications until it is replaced or removed. + * </p> + * + * @param listener the listener to register + * @see ILog#addLogListener + * @see #removeLogListener + */ + public void addLogListener(ILogListener listener); + + /** + * Adds the specified resource to the protection space specified by the + * given realm. All targets at or deeper than the depth of the last + * symbolic element in the path of the given resource URL are assumed to + * be in the same protection space. + * + * @param resourceUrl the URL identifying the resources to be added to + * the specified protection space. For example, + * "http://www.example.com/folder/". + * @param realm the name of the protection space. For example, + * "realm1@example.com" + * @exception CoreException if there are problems setting the + * authorization information. Reasons include: + * <ul> + * <li>The keyring could not be saved.</li> + * </ul> + */ + public void addProtectionSpace(URL resourceUrl, String realm) throws CoreException; + + /** + * Returns a URL which is the local equivalent of the + * supplied URL. This method is expected to be used with + * plug-in-relative URLs returned by IPluginDescriptor. + * If the specified URL is not a plug-in-relative URL, it + * is returned asis. If the specified URL is a plug-in-relative + * URL of a file (incl. .jar archive), it is returned as + * a locally-accessible URL using "file:" or "jar:file:" protocol + * (caching the file locally, if required). If the specified URL + * is a plug-in-relative URL of a directory, + * an exception is thrown. + * + * @param url original plug-in-relative URL. + * @return the resolved URL + * @exception IOException if unable to resolve URL + * @see #resolve + * @see IPluginDescriptor#getInstallURL + */ + public URL asLocalURL(URL url) throws IOException; + + /** + * Removes the authorization information for the specified protection + * space and given authorization scheme. The protection space is defined + * by the given server URL and realm. + * + * @param serverUrl the URL identifying the server to remove the + * authorization information for. For example, + * "http://www.example.com/". + * @param realm the subsection of the given server to remove the + * authorization information for. For example, + * "realm1@example.com" or "" for no realm. + * @param authScheme the scheme for which the authorization information + * to remove applies. For example, "Basic" or "" for no + * authorization scheme. + * @exception CoreException if there are problems removing the + * authorization information. Reasons include: + * <ul> + * <li>The keyring could not be saved.</li> + * </ul> + */ + public void flushAuthorizationInfo(URL serverUrl, String realm, String authScheme) throws CoreException; + + /** + * Returns the adapter manager used for extending + * <code>IAdaptable</code> objects. + * + * @return the adapter manager for this platform + * @see IAdapterManager + */ + public IAdapterManager getAdapterManager(); + + /** + * Returns the authorization information for the specified protection + * space and given authorization scheme. The protection space is defined + * by the given server URL and realm. Returns <code>null</code> if no + * such information exists. + * + * @param serverUrl the URL identifying the server for the authorization + * information. For example, "http://www.example.com/". + * @param realm the subsection of the given server to which the + * authorization information applies. For example, + * "realm1@example.com" or "" for no realm. + * @param authScheme the scheme for which the authorization information + * applies. For example, "Basic" or "" for no authorization scheme + * @return the authorization information for the specified protection + * space and given authorization scheme, or <code>null</code> if no + * such information exists + */ + public Map getAuthorizationInfo(URL serverUrl, String realm, String authScheme); + + /** + * Returns the location of the platform working directory. This + * corresponds to the <i>-data</i> command line argument if + * present or, if not, the current working directory when the platform + * was started. + * + * @return the location of the platform + */ + public IPath getLocation(); + + /** + * Returns the location of the platform log file. This file may contain information + * about errors that have previously occurred during this invocation of the Platform. + * + * Note: it is very important that users of this method do not leave the log + * file open for extended periods of time. Doing so may prevent others + * from writing to the log file, which could result in important error messages + * being lost. It is strongly recommended that clients wanting to read the + * log file for extended periods should copy the log file contents elsewhere, + * and immediately close the original file. + * + * @return the path of the log file on disk. + */ + public IPath getLogFileLocation(); + + /** + * Returns the protection space (realm) for the specified resource, or + * <code>null</code> if the realm is unknown. + * + * @param resourceUrl the URL of the resource whose protection space is + * returned. For example, "http://www.example.com/folder/". + * @return the protection space (realm) for the specified resource, or + * <code>null</code> if the realm is unknown + */ + public String getProtectionSpace(URL resourceUrl); + + /** + * Removes the indicated (identical) log listener from the notification list + * of the platform. If no such listener exists, no action is taken. + * + * @param listener the listener to deregister + * @see ILog#removeLogListener + * @see #addLogListener + */ + public void removeLogListener(ILogListener listener); + + /** + * Returns a URL which is the resolved equivalent of the + * supplied URL. This method is expected to be used with + * plug-in-relative URLs returned by IPluginDescriptor. + * If the specified URL is not a plug-in-relative URL, it is returned + * as is. If the specified URL is a plug-in-relative URL, it is + * resolved to a URL using the actual URL protocol + * (eg. file, http, etc) + * + * @param url original plug-in-relative URL. + * @return the resolved URL + * @exception IOException if unable to resolve URL + * @see #asLocalURL + * @see IPluginDescriptor#getInstallURL + */ + public URL resolve(URL url) throws IOException; + + /** + * Runs the given runnable in a protected mode. Exceptions + * thrown in the runnable are logged and passed to the runnable's + * exception handler. Such exceptions are not rethrown by this method. + * + * @param code the runnable to run + */ + public void run(ISafeRunnable code); + /** + * Returns the log for the given bundle. If no such log exists, one is created. + * + * @return the log for the given bundle + */ + public ILog getLog(Bundle bundle); + + /** + * Returns the platform job manager. + * + * @return the job manager + */ + public IJobManager getJobManager(); + + /** + * Returns URL at which the Platform runtime executables and libraries are installed. + * The returned value is distinct from the location of any given platform's data. + * + * @return the URL indicating where the platform runtime is installed. + */ + public URL getInstallURL(); + + /** + * Returns the location in the filesystem of the configuration information + * used to run this instance of Eclipse. The configuration area typically + * contains the list of plug-ins available for use, various user setttings + * (those shared across different instances of the same configuration) + * and any other such data needed by plug-ins. + * + * @return the path indicating the directory containing the configuration + * metadata for this running Eclipse. + */ + public IPath getConfigurationMetadataLocation(); + + /** + * Takes down the splash screen if one was put up. + */ + public void endSplash(); + + public URL find(Bundle b, IPath path); + + public URL find(Bundle b, IPath path, Map override); + + public InputStream openStream(Bundle b, IPath file) throws IOException; + /** + * Returns an input stream for the specified file. The file path + * must be specified relative to this plug-in's installation location. + * Optionally, the platform searches for the correct localized version + * of the specified file using the users current locale, and Java + * naming convention for localized resource files (locale suffix appended + * to the specified file extension). + * <p> + * The caller must close the returned stream when done. + * </p> + * + * @param file path relative to plug-in installation location + * @param localized <code>true</code> for the localized version + * of the file, and <code>false</code> for the file exactly + * as specified + * @return an input stream + */ + public InputStream openStream(Bundle b, IPath file, boolean localized) throws IOException; + + public IPath getStateLocation(Bundle bundle); + + public ResourceBundle getResourceBundle(Bundle bundle) throws MissingResourceException; + /** + * Returns a resource string corresponding to the given argument value. + * If the argument value specifies a resource key, the string + * is looked up in the default resource bundle. If the argument does not + * specify a valid key, the argument itself is returned as the + * resource string. The key lookup is performed in the + * plugin.properties resource bundle. If a resource string + * corresponding to the key is not found in the resource bundle + * the key value, or any default text following the key in the + * argument value is returned as the resource string. + * A key is identified as a string begining with the "%" character. + * Note, that the "%" character is stripped off prior to lookup + * in the resource bundle. + * <p> + * Equivalent to <code>getResourceString(value, getResourceBundle())</code> + * </p> + * + * @param value the value + * @return the resource string + * @see #getResourceBundle + */ + public String getResourceString(Bundle bundle, String value); + /** + * Returns a resource string corresponding to the given argument + * value and bundle. + * If the argument value specifies a resource key, the string + * is looked up in the given resource bundle. If the argument does not + * specify a valid key, the argument itself is returned as the + * resource string. The key lookup is performed against the + * specified resource bundle. If a resource string + * corresponding to the key is not found in the resource bundle + * the key value, or any default text following the key in the + * argument value is returned as the resource string. + * A key is identified as a string begining with the "%" character. + * Note that the "%" character is stripped off prior to lookup + * in the resource bundle. + * <p> + * For example, assume resource bundle plugin.properties contains + * name = Project Name + * <pre> + * getResourceString("Hello World") returns "Hello World"</li> + * getResourceString("%name") returns "Project Name"</li> + * getResourceString("%name Hello World") returns "Project Name"</li> + * getResourceString("%abcd Hello World") returns "Hello World"</li> + * getResourceString("%abcd") returns "%abcd"</li> + * getResourceString("%%name") returns "%name"</li> + * </pre> + * </p> + * + * @param value the value + * @param bundle the resource bundle + * @return the resource string + * @see #getResourceBundle + */ + public String getResourceString(Bundle bundle, String value, ResourceBundle resourceBundle); +}