/******************************************************************************* * Copyright (c) 2001, 2005 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jem.internal.proxy.remote; /* */ import java.text.MessageFormat; import java.util.*; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jem.internal.proxy.core.*; import org.eclipse.jem.internal.proxy.common.CommandException; import org.eclipse.jem.internal.proxy.common.remote.CommandErrorException; import org.eclipse.jem.internal.proxy.common.remote.Commands; /** * The Remote VM Standard Bean Proxy Factory. * Creation date: (12/3/99 12:01:45 PM) * @author: Joe Winchester */ public final class REMStandardBeanProxyFactory implements IStandardBeanProxyFactory { protected final REMProxyFactoryRegistry fRegistry; protected final REMStandardBeanTypeProxyFactory fBeanTypeProxyFactory; // Convenience field. protected final IREMBeanProxy vmServerProxy; // We need a map for mapping object id's to the proxy. The entry will actually be // a REMStandardBeanProxyFactory.WeakProxyReference. This is so that the // proxy will not be held tightly by this map and can be GC'd if not referenced. // Periodically, the reference queue will be processed to remove any of the // entries that have been GC'd and the server will be told that it can release // the ID on its side. // // This map will be used as synchronize object also for access to it. // // There will be a low priority job that will occasionally process the GC'd objects // and remove them from the queue and the remote vm. It will not remove during lockedTransactions. // // NOTE: Certain proxies never get added to the map. They are the // standard types/class (i.e. null, byte, int, long, short, double, float, byte, string). These // always have the value saved in the proxy so that it can be retrieved without going back // to the server. private final HashMap fIDToProxiesMap = new HashMap(1000); // Map ID's to proxies. The proxies have their id's so we don't need a reverse map private int transactionLockCount = 0; // Count of transactions that have locked access. It is changed under sync control of fIDToProxies map. /** * The Weak reference used for the id to proxies map for the proxy * so that we can clean it up when the proxy has been garbage collected. * * It is important that all changes to the ProxyRef are done through sync on fIDToProxiesMap. */ private static class ProxyRef extends java.lang.ref.WeakReference { private Integer id; // We need the id because this reference will be on the value side // and not the key side of the map, so we need to know the key // so that the object can be removed from the map. // // If the ID is null, then this means this entry has been processed // in some way and should not be released or removed from the table. private int useCnt = 1; // Usage/Ref count. Needed so that we don't get rid of too soon. Since we don't // create a ProxyRef without a usage, we start it off with one. It will be incremented // then on retrievals when needed by users outside of the remote factories themselves. // It won't be physically released until either GC got rid of it, or it goes to 0 or less // on a release request. public ProxyRef(Integer anID, Object proxy, java.lang.ref.ReferenceQueue q) { super(proxy, q); id = anID; } public Integer getID() { return id; } public void clear() { super.clear(); id = null; } public synchronized void incrUseCnt() { useCnt++; } public synchronized int decrUseCnt() { if (--useCnt < 0) useCnt = 0; return useCnt; } } /* Reference queue for cleared Proxies */ private java.lang.ref.ReferenceQueue queue = new java.lang.ref.ReferenceQueue(); /** * Process the entries on the id to proxies map that have been garbage collected. * It is package-protected because only REMRegistryController should call it. */ void processQueue() { ProxyRef pr; while (true) { if (Thread.interrupted()) return; // Maybe going down. (This actually a kludge because the thread happens to be the master thread from the registry controller). synchronized (fIDToProxiesMap) { if (queue == null || transactionLockCount > 0) break; // Either no queue (we are cleaning up) or in a transaction, stop processing and retry on next time slice. if ((pr = (ProxyRef) queue.poll()) != null) { if (pr.getID() != null) { // It hasn't been processed by some other means. fIDToProxiesMap.remove(pr.getID()); releaseID(pr.getID().intValue()); } } else break; // There are no more waiting, so leave. } } } /** * REMBeanProxyFactory constructor comment. * * Note: It assumes the beantype factory has already been registered. */ REMStandardBeanProxyFactory(REMProxyFactoryRegistry aRegistry) { fRegistry = aRegistry; aRegistry.registerBeanProxyFactory(this); fBeanTypeProxyFactory = (REMStandardBeanTypeProxyFactory) aRegistry.getBeanTypeProxyFactory(); fBeanTypeProxyFactory.initialize(this); // Now we're ready for the beantype factory to be initialized. IREMBeanTypeProxy serverType = fBeanTypeProxyFactory.objectClass.newBeanTypeForClass(new Integer(Commands.REMOTEVMSERVER_CLASS), "org.eclipse.jem.internal.proxy.vm.remote.RemoteVMServerThread", false); //$NON-NLS-1$ fBeanTypeProxyFactory.registerBeanTypeProxy(serverType, true); vmServerProxy = serverType.newBeanProxy(new Integer(Commands.REMOTESERVER_ID)); registerProxy(vmServerProxy); } /** * Register a collection of Proxies */ public void registerProxies(Collection proxies) { synchronized(fIDToProxiesMap) { Iterator itr = proxies.iterator(); while (itr.hasNext()) { IREMBeanProxy proxy = (IREMBeanProxy) itr.next(); if (proxy instanceof IBeanTypeProxy || !(proxy.getTypeProxy() instanceof IREMConstantBeanTypeProxy)) { ProxyRef oldRef = (ProxyRef) fIDToProxiesMap.put(proxy.getID(), new ProxyRef(proxy.getID(), proxy, queue)); if (oldRef != null) { // We've replaced it with a new one, so we will clear out the ref so that it won't later try to remove itself oldRef.clear(); } } } } } /** * Register a single proxy */ public void registerProxy(IREMBeanProxy proxy) { if (proxy instanceof IBeanTypeProxy || !(proxy.getTypeProxy() instanceof IREMConstantBeanTypeProxy)) synchronized(fIDToProxiesMap) { ProxyRef oldRef = (ProxyRef) fIDToProxiesMap.put(proxy.getID(), new ProxyRef(proxy.getID(), proxy, queue)); if (oldRef != null) { // We've replaced it with a new one, so we will clear out the ref so that it won't later try to remove itself oldRef.clear(); } } } /** * Release a proxy because no longer needed. */ public void releaseProxy(IBeanProxy proxy) { if (!proxy.isValid()) return; if (proxy instanceof IBeanTypeProxy && !fBeanTypeProxyFactory.releaseProxy((IBeanTypeProxy) proxy)) return; // BeanType and type factory won't allow release of it. Integer id = ((IREMBeanProxy) proxy).getID(); synchronized(fIDToProxiesMap) { // Synced in here so that we will remove it before some one else from a different thread may try // to access it again. if (id.intValue() != Commands.NOT_AN_ID) { ProxyRef ref = (ProxyRef) fIDToProxiesMap.get(id); if (ref == null || ref.decrUseCnt() <= 0) { // No usage, so do actual release. fIDToProxiesMap.remove(id); ((IREMBeanProxy) proxy).release(); if (ref != null) ref.enqueue(); // Queue it up so that on next release cycle it will go away. } } else { ((IREMBeanProxy) proxy).release(); } } } /** * Release a specific ID. This is used in case an ID has been sent * but we couldn't proxy it. In that case we only have an ID. It is * also used when a proxy has been released explicitly or through GC. * In that case it has already been de-registered. */ private void releaseID(int anID) { try { IREMConnection connect = fRegistry.getFreeConnection(); try { connect.releaseID(anID); } finally { fRegistry.returnConnection(connect); } } catch (IllegalStateException e) { // Couldn't get connection, don't bother with a release. } } /** * For the Remote Factory we will create an REMBeanProxy using the null constructor * Package protected so only REMBeanTypeProxies can create instances. */ IBeanProxy createBeanProxy(IREMBeanTypeProxy aTypeProxy) throws ThrowableProxy { return REMStandardBeanProxyConstants.getConstants(fRegistry).getClassNewInstance().invoke(null, aTypeProxy); } /** * For the Remote Factory we will create a REMBeanProxy using the initializationString. * Package protected so only REMBeanTypeProxies can create instances. */ IBeanProxy createBeanProxy(IREMBeanTypeProxy aTypeProxy, String initializationString) throws ThrowableProxy, CommandException, ClassCastException, InstantiationException, IllegalStateException { IREMConnection connect = fRegistry.getFreeConnection(); startTransaction(); // Starting a transaction, we will be getting id's back and need to get data in a separate step, so we need to group it in a transaction. try { Commands.ValueObject newInstanceData = null; try { newInstanceData = getNewInstanceData(aTypeProxy, initializationString, connect); } catch (CommandErrorException e) { switch (e.getErrorCode()) { case Commands.CLASS_CAST_EXCEPTION: // The result was not of the correct type. throw new ClassCastException( MessageFormat.format(ProxyRemoteMessages.Classcast_EXC_, new Object[] {extractFirstLine(initializationString), aTypeProxy.getTypeName()})); case Commands.CANNOT_EVALUATE_STRING: // Want to log the exception that caused it to not evaluate. // Don't need to log this side, just log the RemoteVM side of the trace. java.io.StringWriter s = new java.io.StringWriter(); java.io.PrintWriter w = new java.io.PrintWriter(s); ((ThrowableProxy) e.getErrorObject()).printProxyStackTrace(w); ProxyPlugin.getPlugin().getLogger().log(new Status(IStatus.WARNING, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, s.toString(), null)); throw new InstantiationException( MessageFormat.format(ProxyRemoteMessages.Instantiate_EXC_, new Object[] {extractFirstLine(initializationString)})); default: throw e; //$NON-NLS-1$ } } catch (CommandException e) { if (e.isRecoverable()) { ProxyPlugin.getPlugin().getLogger().log( new Status( IStatus.WARNING, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, "", //$NON-NLS-1$ e)); } else { // It failed in the command, try again. fRegistry.closeConnection(connect); connect = null; connect = fRegistry.getFreeConnection(); try { newInstanceData = getNewInstanceData(aTypeProxy, initializationString, connect); } catch (CommandException eAgain) { // It failed again. Close this connection and don't let it be returned. fRegistry.closeConnection(connect); connect = null; // This is so that it won't be returned. } } } finally { if (connect != null) fRegistry.returnConnection(connect); } if (newInstanceData != null) return getBeanProxy(newInstanceData); // Turn it into a proxy } finally { stopTransaction(); // Now we can release the transaction. } return null; } private String extractFirstLine(String initString) { // Need to extract the first line for the messageFormat not to barf. int cr = initString.indexOf('\r'); int lf = initString.indexOf('\n'); if (cr != -1 || lf != -1) { if (cr == -1 || (lf != -1 && lf < cr)) return initString.substring(0, lf); else return initString.substring(0, cr); } else return initString; } /** * actually create it using a passed in connection. This allows retry capability. * * This will send the request over to the connection. * * If we get an OBJECT back, then the beantypeproxy that the classID in the * value points to must be of type IREMConstantBeanTypeProxy so that we can * send this new object to the correct beantype to create the correct proxy. * * If we get an OBJECT_ID back, then the beantypeproxy that the classID in * the value points to must be able to handle creating a proxy of that type. */ private Commands.ValueObject getNewInstanceData(IREMBeanTypeProxy aTypeProxy, String initializationString, IREMConnection connect) throws ThrowableProxy, CommandException { try { Commands.ValueObject newInstanceData = new Commands.ValueObject(); connect.getNewInstance(aTypeProxy.getID().intValue(), initializationString, newInstanceData); return newInstanceData; } catch (CommandErrorException e) { // TBD - Needs to handle error of not evaluatable and send over to the compilor. processErrorReturn(e); // Process this } return null; } /** * actually create it using a passed in connection. This allows retry capability. */ private void getObjectInstanceData(IREMConnection connect, int objectID, Commands.ValueObject valueReturn) throws ThrowableProxy, CommandException { try { connect.getObjectData(objectID, valueReturn); } catch (CommandErrorException e) { processErrorReturn(e); // Process this } } /** * Get a bean proxy from the value object passed in. If not yet created, create one. * NOTE: NULL type actually returns a true null. This is so that if people are casting * the returned proxy to a specific type (e.g IIntegerBeanProxy), then they won't get * a ClassCastException, they will get a null. This is easier for them to handle. * * NOTE: This is public ONLY so that extension factories can create correct types of * proxies in consistent manner from a value object. * * It is important that this is called * from within a transaction only because otherwise the value could have invalid data * by the time we try to get the data out of it. */ public IBeanProxy getBeanProxy(Commands.ValueObject value) throws ThrowableProxy, CommandException { switch (value.type) { // Null result. case (byte) Commands.VOID_TYPE: return null; // A constant object was received. case Commands.OBJECT: IREMConstantBeanTypeProxy constantBeanType = null; try { constantBeanType = (IREMConstantBeanTypeProxy) getBeanType(value.classID); if (constantBeanType == null) return null; // Cannot find the type to create it. } catch (ClassCastException e) { // It wasn't a constant type, so we can't create it. Return null. ProxyPlugin.getPlugin().getLogger().log(new Status(IStatus.WARNING, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, "", e)); //$NON-NLS-1$ return null; } return constantBeanType.newBeanProxy(value); // An existing object_id was returned, the object should exist. If it doesn't // then submit the info command to try to recreate it correctly. case Commands.OBJECT_ID: Integer objectID = new Integer(value.objectID); IBeanProxy proxy = retrieveProxy(objectID, true); if (proxy != null) return proxy; // Not found, need to try to recreate it. IREMConnection connect = fRegistry.getFreeConnection(); try { getObjectInstanceData(connect, value.objectID, value); // Go and get the data } catch (CommandErrorException e) { if (e.isRecoverable()) { ProxyPlugin.getPlugin().getLogger().log( new Status( IStatus.WARNING, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, "", //$NON-NLS-1$ e)); return null; } else { // Try one more time. fRegistry.closeConnection(connect); connect = null; connect = fRegistry.getFreeConnection(); try { getObjectInstanceData(connect, value.objectID, value); // Go and get the data } catch (CommandErrorException eAgain) { fRegistry.closeConnection(connect); connect = null; throw eAgain; } } } finally { if (connect != null) fRegistry.returnConnection(connect); } return getBeanProxy(value); // Now process it to create the new data. // An new object id. Need to get the class type and let it create it. case Commands.NEW_OBJECT_ID: try { IREMBeanTypeProxy newBeanType = getBeanType(value.classID); IREMBeanProxy newProxy = newBeanType.newBeanProxy(new Integer(value.objectID)); if (newProxy != null) registerProxy(newProxy); return newProxy; } catch (CommandException e) { // The server has created a new object, but we couldn't create/register a proxy for it. // We need to release it so that the server can get rid of it. Otherwise it would hang // around over there forever. releaseID(value.objectID); throw e; } catch (RuntimeException e) { // The server has created a new object, but we couldn't create/register a proxy for it. // We need to release it so that the server can get rid of it. Otherwise it would hang // around over there forever. releaseID(value.objectID); throw e; } // An exception was thrown, create the ThrowableProxy and then throw it. case Commands.THROW: IREMBeanProxy newThrowProxy = null; try { IREMBeanTypeProxy newThrowType = getBeanType(value.classID); newThrowProxy = newThrowType.newBeanProxy(new Integer(value.objectID)); if (newThrowProxy != null) registerProxy(newThrowProxy); } catch (CommandException e) { // The server has created a new object, but we couldn't create/register a proxy for it. // We need to release it so that the server can get rid of it. Otherwise it would hang // around over there forever. releaseID(value.objectID); throw e; } catch (RuntimeException e) { // The server has created a new object, but we couldn't create/register a proxy for it. // We need to release it so that the server can get rid of it. Otherwise it would hang // around over there forever. releaseID(value.objectID); throw e; } // It really should be a throwable, but if not, just return it. if (newThrowProxy instanceof ThrowableProxy) throw (ThrowableProxy) newThrowProxy; else return newThrowProxy; // It is one of the standard kinds, which are Constant types default: IREMConstantBeanTypeProxy standardBeanType = null; try { standardBeanType = (IREMConstantBeanTypeProxy) getBeanType(value.type); if (standardBeanType == null) return null; // Cannot find the type to create it. } catch (ClassCastException e) { // It wasn't a standard type, so we can't create it. Return null. ProxyPlugin.getPlugin().getLogger().log(new Status(IStatus.WARNING, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, "", e)); //$NON-NLS-1$ return null; } return standardBeanType.newBeanProxy(value); } } /** * Process the error exception. Get the data from it and turn it into proxy data. * If it is a THROW, then it will throw the Throwable instead. * * NOTE: It is public ONLY so that extension factories can process errors in * an consistent manner. * * It is important that this be called only within a transaction. */ public void processErrorReturn(CommandErrorException e) throws CommandException, ThrowableProxy { int code = e.getErrorCode(); Object data = null; if (code == Commands.THROWABLE_SENT) data = getBeanProxy(e.getValue()); // It is Throw sent, so let the throw from getBeanProxy continue on out. (But as a safety, we still try to get the data. else { try { data = getBeanProxy(e.getValue()); } catch (ThrowableProxy t) { // But we want to keep throwables in this case. They are just data for the command error. data = t; } } throw new CommandErrorException(MessageFormat.format(ProxyRemoteMessages.RemoteCmd_EXC_, new Object[] {new Integer(code)}), code, e.getValue(), data); // Create a new one and throw it containing the proxied data. //$NON-NLS-1$ } /** * Get a beantype where the id passed in is the classID of the beantype. * If not found, then go get it loaded. If it can't be found, then we will * release the id because it means we have an id from the server, but we * can't create a proxy for it, so don't keep the server holding it. * NOTE: This is public ONLY so that extension factories can create correct types * in a consistent manner from the id. * * It is important that this be called only from within a transaction. */ public IREMBeanTypeProxy getBeanType(int id) throws CommandException { IREMBeanTypeProxy beanType = null; try { Integer classID = new Integer(id); beanType = (IREMBeanTypeProxy) retrieveProxy(classID, false); if (beanType == null) beanType = fBeanTypeProxyFactory.createBeanTypeProxy(classID); // We don't have it, need to go to the factory so that it can go query what it needs if (beanType == null) return null; // Cannot find the type to create it. } catch (ClassCastException e) { // It wasn't a bean type, so we can't create it. Return null. ProxyPlugin.getPlugin().getLogger().log(new Status(IStatus.WARNING, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, "", e)); //$NON-NLS-1$ } finally { if (beanType == null) releaseID(id); // Couldn't create a proxy, so release the id. } return beanType; } /** * Return a proxy wrapping the primitive integer */ public IIntegerBeanProxy createBeanProxyWith(int aPrimitiveInteger) { return fBeanTypeProxyFactory.intType.createIntegerBeanProxy(aPrimitiveInteger); } /** * Return a proxy wrapping the primitive byte */ public INumberBeanProxy createBeanProxyWith(byte aPrimitiveByte) { return fBeanTypeProxyFactory.byteType.createByteBeanProxy(aPrimitiveByte); } /** * Return a proxy wrapping the primitive char */ public ICharacterBeanProxy createBeanProxyWith(char aPrimitiveChar) { return fBeanTypeProxyFactory.charType.createCharacterBeanProxy(aPrimitiveChar); } /** * Return a proxy wrapping the primitive short */ public INumberBeanProxy createBeanProxyWith(short aPrimitiveShort) { return fBeanTypeProxyFactory.shortType.createShortBeanProxy(aPrimitiveShort); } /** * Return a proxy wrapping the primitive long */ public INumberBeanProxy createBeanProxyWith(long aPrimitiveLong) { return fBeanTypeProxyFactory.longType.createLongBeanProxy(aPrimitiveLong); } /** * Return a proxy wrapping the primitive double */ public INumberBeanProxy createBeanProxyWith(double aPrimitiveDouble) { return fBeanTypeProxyFactory.doubleType.createDoubleBeanProxy(aPrimitiveDouble); } /** * Return a proxy wrapping the primitive float */ public INumberBeanProxy createBeanProxyWith(float aPrimitiveFloat) { return fBeanTypeProxyFactory.floatType.createFloatBeanProxy(aPrimitiveFloat); } /** * createBeanProxyWith method comment. */ public IBooleanBeanProxy createBeanProxyWith(Boolean aBoolean) { return fBeanTypeProxyFactory.booleanClass.createBooleanBeanProxy(aBoolean); } /** * Return a proxy that wraps the Integer argument */ public IIntegerBeanProxy createBeanProxyWith(Integer anInteger) { return fBeanTypeProxyFactory.integerClass.createIntegerBeanProxy(anInteger); } /** * createBeanProxyWith method comment. */ public INumberBeanProxy createBeanProxyWith(Number aNumber) { REMAbstractNumberBeanTypeProxy type = (REMAbstractNumberBeanTypeProxy) fBeanTypeProxyFactory.getBeanTypeProxy(aNumber.getClass().getName()); return type.createNumberBeanProxy(aNumber); } /** * Return a proxy for the argument */ public IStringBeanProxy createBeanProxyWith(String aString) { return fBeanTypeProxyFactory.stringClass.createStringBeanProxy(aString); } /** * Return a proxy for the argument */ public ICharacterBeanProxy createBeanProxyWith(Character aCharacter) { return fBeanTypeProxyFactory.characterClass.createCharacterBeanProxy(aCharacter); } /** * createBeanProxyWith method comment. */ public IBooleanBeanProxy createBeanProxyWith(boolean aPrimitiveBoolean) { return fBeanTypeProxyFactory.booleanType.createBooleanBeanProxy(aPrimitiveBoolean); } /** * Create an array bean proxy. * * - (int, new int[2] {3, 4}) will create: * int [3] [4] * * - (int[], new int[1] {1}) * int [1] * * - (int[], new int[2] {2,3}) * int [2] [3] * * * - (int[], null) or (int[], new int[0]) or (int, null) or (int, new int[0]) * int [0]... * or * (int[][]..., null) or (int[][]..., new int[0]) * int[0][]... * This is because an array instance with no specified dimensions is not valid. * * - (int[][], new int[1] {3}) * int[3][] */ public IArrayBeanProxy createBeanProxyWith(IBeanTypeProxy type, int[] dimensions) throws ThrowableProxy { if (type.isArray()) return ((REMArrayBeanTypeProxy) type).createBeanProxyWith(dimensions); // Already an array type, just pass it on. else { // It is not an array type, so we need to get an array of this type and dimensions. REMArrayBeanTypeProxy arrayType = (REMArrayBeanTypeProxy) fBeanTypeProxyFactory.getBeanTypeProxy(type.getTypeName(), dimensions.length); return arrayType.createBeanProxyWith(dimensions); } } /** * Create a one-dimensional array. * The result will be the same as calling * createBeanProxyWith(IBeanTypeProxy type, new int[1] {x}) * where 'x' is the value passed in as the dimension. */ public IArrayBeanProxy createBeanProxyWith(IBeanTypeProxy type, int dimension) throws ThrowableProxy { return createBeanProxyWith(type, new int[] {dimension}); } /** * Retrieve the proxy from the mapping table. Handle already GC'd. * If this returns null, it is important that the caller tries to recreate * it since the id is still valid on the server. */ private IBeanProxy retrieveProxy(Integer objectID, boolean incrementUseCnt) { synchronized (fIDToProxiesMap) { ProxyRef ref = (ProxyRef) fIDToProxiesMap.get(objectID); if (ref != null) { Object bp = ref.get(); if (ref.isEnqueued() || bp == null) { // It's waiting to be removed, or has been removed. i.e. GC'd, so just remove it from the map, next processQueue will remove it from the queue. fIDToProxiesMap.remove(objectID); ref.clear(); // This is so that when the processQueue see's it, // it won't send a release request to the server // since it will be recreated when this method returns. return null; } else { if (incrementUseCnt) ref.incrUseCnt(); return (IBeanProxy) bp; } } else return null; } } /** * Start Transaction: During the time between start/stop transaction, * proxies will not be cleaned up. This prevents the case of a two step * transaction where the returned ID from the remote vm is for a proxy * that is about to be cleaned up, and then having that proxy disappear * when going for the data for it. * * Note: It is IMPERITIVE that the start/stop is used in the following way: * factory.startTransaction(); * try { * do your stuff... * } finally { * factory.stopTransaction(); * } * * This way it can never accidently leave it in a locked state. */ public void startTransaction() { synchronized (fIDToProxiesMap) { transactionLockCount++; } } public boolean inTransaction() { synchronized (fIDToProxiesMap) { return transactionLockCount != 0; } } /** * Stop Transaction: During the time between start/stop transaction, * proxies will not be cleaned up. This prevents the case of a two step * transaction where the returned ID from the remote vm is for a proxy * that is about to be cleaned up, and then having that proxy disappear * when going for the data for it. * * Note: It is IMPERITIVE that the start/stop is used in the following way: * factory.startTransaction(); * try { * do your stuff... * } finally { * factory.stopTransaction(); * } * * This way it can never accidently leave it in a locked state. */ public void stopTransaction() { synchronized (fIDToProxiesMap) { if (--transactionLockCount < 0) transactionLockCount = 0; // Shouldn't occur, but just in case. } } /** * Terminate the factory. If this is being terminated, then the server is being terminated too. * So just go through all of the proxies and release them, but don't send any notification to * the server since the server is subsequently just going to throw them away when it terminates. * <p> * This can't run async, so wait is a suggestion here. */ public void terminateFactory(boolean wait) { synchronized (fIDToProxiesMap) { Iterator itr = fIDToProxiesMap.values().iterator(); while (itr.hasNext()) { ProxyRef ref = (ProxyRef) itr.next(); if (ref != null) { Object bp = ref.get(); // If not cleaned up and not enqueued, release it. if (bp != null && !ref.isEnqueued()) ((IREMBeanProxy) bp).release(); } } fIDToProxiesMap.clear(); queue = null; // Don't bother processing the queue, don't need to now. } } /* (non-Javadoc) * @see org.eclipse.jem.internal.proxy.core.IStandardBeanProxyFactory#createExpression() */ public IExpression createExpression() { return new REMExpression(this.fRegistry); } /* (non-Javadoc) * @see org.eclipse.jem.internal.proxy.core.IStandardBeanProxyFactory#createBeanProxyFrom(java.lang.String) */ public IBeanProxy createBeanProxyFrom(String initializationString) throws ThrowableProxy, ClassCastException, InstantiationException { try { return createBeanProxy(fBeanTypeProxyFactory.voidType, initializationString); } catch (CommandException e) { ProxyPlugin.getPlugin().getLogger().log(new Status(IStatus.WARNING, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, "", e)); //$NON-NLS-1$ } } return null; } /* * (non-Javadoc) * @see org.eclipse.jem.internal.proxy.core.IStandardBeanProxyFactory#convertToPrimitiveBeanProxy(org.eclipse.jem.internal.proxy.core.IBeanProxy) */ public IBeanProxy convertToPrimitiveBeanProxy(IBeanProxy nonPrimitiveProxy) { if (nonPrimitiveProxy == null) return null; if (!nonPrimitiveProxy.isValid()) return nonPrimitiveProxy; IREMBeanTypeProxy type = (IREMBeanTypeProxy) nonPrimitiveProxy.getTypeProxy(); // Step into the internals. The ID is a constant int, so we can use a switch stmt. switch (type.getID().intValue()) { case Commands.BOOLEAN_CLASS: return this.createBeanProxyWith(((IBooleanBeanProxy) nonPrimitiveProxy).booleanValue()); case Commands.BYTE_CLASS: return this.createBeanProxyWith(((INumberBeanProxy) nonPrimitiveProxy).byteValue()); case Commands.CHARACTER_CLASS: return this.createBeanProxyWith(((ICharacterBeanProxy) nonPrimitiveProxy).charValue()); case Commands.DOUBLE_CLASS: return this.createBeanProxyWith(((INumberBeanProxy) nonPrimitiveProxy).doubleValue()); case Commands.FLOAT_CLASS: return this.createBeanProxyWith(((INumberBeanProxy) nonPrimitiveProxy).floatValue()); case Commands.INTEGER_CLASS: return this.createBeanProxyWith(((INumberBeanProxy) nonPrimitiveProxy).intValue()); case Commands.LONG_CLASS: return this.createBeanProxyWith(((INumberBeanProxy) nonPrimitiveProxy).longValue()); case Commands.SHORT_CLASS: return this.createBeanProxyWith(((INumberBeanProxy) nonPrimitiveProxy).shortValue()); default: return nonPrimitiveProxy; } } public IBeanProxy getIVMServerProxy() { return vmServerProxy; } }