package net.sourceforge.gjtapi; // NAME // $RCSfile$ // DESCRIPTION // [given below in javadoc format] // DELTA // $Revision$ // CREATED // $Date$ // COPYRIGHT // Westhawk Ltd // TO DO // // part of Free Jtapi. /* * Copyright (C) 1999 by Westhawk Ltd (www.westhawk.co.uk) * * Permission to use, copy, modify, and distribute this software * for any purpose and without fee is hereby granted, provided * that the above copyright notices appear in all copies and that * both the copyright notice and this permission notice appear in * supporting documentation. * This software is provided "as is" without express or implied * warranty. */ import javax.telephony.*; import javax.telephony.events.*; import javax.telephony.capabilities.AddressCapabilities; import net.sourceforge.gjtapi.events.*; import net.sourceforge.gjtapi.capabilities.GenAddressCapabilities; import java.lang.ref.WeakReference; import java.util.*; import javax.telephony.privatedata.PrivateData; public class FreeAddress implements Address, PrivateData { private String name; private Provider provider; private boolean local; // Is this an Address in the Provider's domain? private Vector<CallObserver> callObservers = new Vector<CallObserver>(); private ObservableHelper observers = new ObservableHelper() { Object[] mkObserverArray(int i) { return new AddressObserver[i]; } void notifyObserver(Object o, Ev [] e) { final AddressObserver obs = (AddressObserver) o; obs.addressChangedEvent((AddrEv[])e); } }; private Set<TermData> terminals = null; // holds the TermData of the terminals private Vector<ConnectionHolder> connections = new Vector<ConnectionHolder>(2); // holds weak references to the Connection private transient Vector<CallListener> callListeners = new Vector<CallListener>(); private transient Vector<AddressListener> addressListeners = new Vector<AddressListener>(); private boolean reporting = false; /** * A weak reference to a Connection that can re-instantiate itself as necessary **/ class ConnectionHolder { private final CallId callId; private final String address; final WeakReference<FreeConnection> connRef; ConnectionHolder(CallId id, String addr) { callId = id; address = addr; connRef = null; } ConnectionHolder(FreeConnection connection) { callId = ((FreeCall)connection.getCall()).getCallID(); address = connection.getAddress().getName(); connRef = new WeakReference<FreeConnection>(connection); } CallId getCallId() { return callId; } private String getAddress() { return address; } /** * Return the current referrent or null **/ private FreeConnection getReferent() { WeakReference<FreeConnection> wr = connRef; FreeConnection conn = null; if (wr != null) { conn = (FreeConnection)wr.get(); } return conn; } public int hashCode() { return this.getCallId().hashCode() + this.getAddress().hashCode(); } public boolean equals(Object o) { if (o instanceof ConnectionHolder) { ConnectionHolder ch = (ConnectionHolder)o; if (ch.getCallId() != null && ch.getCallId().equals(this.getCallId()) && ch.getAddress().equals(this.getAddress())) return true; } return false; } FreeConnection getConnection() { FreeConnection conn = getReferent(); if (conn == null) { synchronized (this) { // double check conn = getReferent(); if (conn == null) { conn = ((GenericProvider)getProvider()).getCallMgr().getFaultedConnection(this.getCallId(), this.getAddress()); } } } return conn; } } /** * Create an Address object. * This should only be called by the DomainMgr. * @param num The Address's unique number, often a phone number * @param prov The Provider that hold the Address's domain. * @param local Is the Address local to the Provider's domain? **/ FreeAddress(String num, Provider prov, boolean local){ super(); this.setProvider(prov); this.setName(num); this.setLocal(local); } @SuppressWarnings("unchecked") public synchronized void addAddressListener(AddressListener l) { if (l == null) return; Vector<AddressListener> v = addressListeners == null ? new Vector<AddressListener>(2) : (Vector<AddressListener>) addressListeners.clone(); // protect the Address from garbage collection this.protect(); // add the listener if (!v.contains(l)) { v.addElement(l); addressListeners = v; } } public void addCallListener(CallListener l) throws ResourceUnavailableException,MethodNotSupportedException { synchronized (callListeners) { /* add to our list */ if (!callListeners.contains(l)) { callListeners.addElement(l); this.startEvents(); } /* and add to any existing calls */ Call [] cs = getCalls(); if (cs != null){ for (int i = 0; i<cs.length;i++){ ((FreeCall)cs[i]).addCallListener(l, this); } } } } public void addCallObserver(CallObserver observer) throws javax.telephony.ResourceUnavailableException, javax.telephony.MethodNotSupportedException { callObservers.add(observer); this.startEvents(); /* and add to any existing calls */ Call [] cs = getCalls(); if (cs != null){ for (int i = 0; i<cs.length;i++){ ((FreeCall)cs[i]).addCallObserver(observer, this); } } } void addConnection(FreeConnection con) { Call call = con.getCall(); CallListener[] cl = getCallListeners(); if (cl != null){ final FreeCall freeCall = (FreeCall) call; for (CallListener current : cl) { freeCall.addCallListener(current, this); } } CallObserver[] cobs = getCallObservers(); if (cobs != null) { final FreeCall freeCall = (FreeCall) call; for (CallObserver current : cobs) { freeCall.addCallObserver(current, this); } } final ConnectionHolder holder = new ConnectionHolder(con); connections.addElement(holder); } /** * Add an old-style Observer to the Address object. * This will report Observation ended events and little else. * <P>Fixed casting: Loius Gibson, June 9, 2000 * * @param observer The AddressObserver that will receive the update callbacks. * **/ public void addObserver(AddressObserver observer) throws javax.telephony.ResourceUnavailableException, javax.telephony.MethodNotSupportedException { // check if we need protection if (observer != null) { this.protect(); observers.addObserver(observer); } } /** * this is an atypical fire method - in that it only fires on a single * listener, not all of them. */ protected void fireAddressListenerEnded(AddressListener l ) { AddressEvent e = new FreeAddrObsEndedEv(Event.CAUSE_NORMAL, // cause Ev.META_UNKNOWN, // what is the meta-event false, // is metaevent this); // FreeAddress l.addressListenerEnded(e); } public AddressCapabilities getAddressCapabilities(Terminal terminal) throws javax.telephony.InvalidArgumentException, javax.telephony.PlatformException { return getCapabilities(); } public AddressListener[] getAddressListeners() { Vector<AddressListener> listeners = this.addressListeners; if((listeners == null) || (listeners.size() == 0)) { return null; } return listeners.toArray(new AddressListener[listeners.size()]); } public CallListener[] getCallListeners() { Vector<CallListener> listeners = this.callListeners; if((listeners == null) || (listeners.size() == 0)) { return null; } synchronized (listeners) { return listeners.toArray(new CallListener[listeners.size()]); } } public CallObserver[] getCallObservers() { Vector<CallObserver> listeners = this.callObservers; if((listeners == null) || (listeners.size() == 0)) { return null; } synchronized (listeners) { return listeners.toArray(new CallObserver[listeners.size()]); } } /** * utility routine to get all calls associated with our connections */ Call [] getCalls(){ Call [] ret = null; Connection [] cons = this.getConnections(); if (cons != null){ Vector<Call> v = new Vector<Call>(cons.length); for (int i=0; i< cons.length; i++){ Call ca = cons[i].getCall(); if (ca != null){ v.addElement(ca); } } ret = new Call[v.size()]; v.copyInto(ret); } return ret; } /** * Returns the dynamic capabilities for this instance of the Address object. Dynamic capabilities tell the application which actions are * possible at the time this method is invoked based upon the implementations knowledge of its ability to successfully perform the * action. This determination may be based upon argument passed to this method, the current state of the call model, or some * implementation-specific knowledge. These indications do not guarantee that a particular method will succeed when invoked, * however. **/ public AddressCapabilities getCapabilities() { return ((GenAddressCapabilities)this.getProvider().getAddressCapabilities()).getDynamic(this); } /** * Transform the collection of weak ConnectionHolders into an array of Connection objects. **/ public Connection[] getConnections() { Connection[] ret = null; // check if we need to ask the raw TelephonyProvider to flush in my calls CallMgr cm = ((GenericProvider)this.getProvider()).getCallMgr(); if (cm.isDynamic()) { cm.loadCalls(this); } // now dereference all my Connections synchronized (connections) { ret = new Connection[connections.size()]; // check if the array is empty if (ret.length == 0) { return null; } Iterator<ConnectionHolder> it = connections.iterator(); int i = 0; while (it.hasNext()) { ret[i] = ((ConnectionHolder) it.next()).getConnection(); i++; } } return ret; } /** * Find or create a connection associated with a call * Creation date: (2000-02-15 13:38:51) * @author: Richard Deadman * @return A found or new Connection * @param call The call the connection should be hooked to */ FreeConnection getLazyConnection(FreeCall call) { // look for existing one Vector<ConnectionHolder> v = this.connections; ConnectionHolder testHolder = new ConnectionHolder(call.getCallID(), this.getName()); if (v.contains(testHolder)) { // it's in there... find it Iterator<ConnectionHolder> it = v.iterator(); while (it.hasNext()) { ConnectionHolder ch = (ConnectionHolder)it.next(); if (ch.equals(testHolder)) return ch.getConnection(); } } // No connection found -- create a new one return new FreeConnection(call, this); } public String getName() { return name; } public AddressObserver[] getObservers() { return (AddressObserver[]) observers.getObjects(); } /** * Get any PrivateData associated with my low-level object. */ public Object getPrivateData() { return ((GenericProvider)this.getProvider()).getRaw().getPrivateData(null, this.getName(), null); } public Provider getProvider() { return provider; } /** * Return the set of Terminals associated with the Address. IF these haven't been fetched yet, * ask the raw provider for them. **/ public Terminal[] getTerminals() { // check if we need to retrieve the Terminal list if (this.terminals == null) { synchronized (this) { // now double check if (this.terminals == null) { this.terminals = new HashSet<TermData>(1); try { TermData[] terms = ((GenericProvider) this.getProvider()).getRaw().getTerminals(this.getName()); if (terms != null) { this.setTerminalData(terms); } } catch (InvalidArgumentException iae) { // no terminals -- but leave instantiated so we don't check again. } } } } // transform collection of Terminal names to array of terminals synchronized (terminals) { Terminal[] ret = null; if (this.terminals.isEmpty()) return null; ret = new Terminal[terminals.size()]; Iterator<TermData> it = terminals.iterator(); int i = 0; while (it.hasNext()) { TermData termData = (TermData)it.next(); ret[i] = ((GenericProvider) this.getProvider()).getDomainMgr().getLazyTerminal(termData.terminal, termData.isMedia); i++; } return ret; } } /** * Is this a local Address? * Creation date: (2000-06-22 9:44:13) * @author: Richard Deadman * @return true if the Address is in the Provider's domain. */ boolean isLocal() { return local; } /** * Determine if I have any current Observers or Listeners. **/ private boolean isObserved() { // test if we should protect the Address if (((this.addressListeners == null) || (this.addressListeners.size() == 0)) && ((this.observers == null) || (this.observers.size() == 0))) { return false; } else { return true; } } /** * protect this address from garbage collection if dynamic Address memory is supported **/ private void protect() { // test if we are about to add the first observer if (!isObserved()) { ((GenericProvider)this.getProvider()).getDomainMgr().protect(this); } } /** * Remove an AddressListener from the Address. * If this is the last listener or observer, allow the Address to be garbage collected if the domain * support its. **/ @SuppressWarnings("unchecked") public synchronized void removeAddressListener(AddressListener l) { if (addressListeners != null && addressListeners.contains(l)) { Vector<AddressListener> v = (Vector<AddressListener>) addressListeners.clone(); v.removeElement(l); addressListeners = v; fireAddressListenerEnded(l); // test if we can free for potential garbage collection this.unProtect(); } } @SuppressWarnings("unchecked") public synchronized void removeCallListener(CallListener l) { if (callListeners.contains(l)) { Vector<CallListener> v = (Vector<CallListener>) callListeners.clone(); v.removeElement(l); callListeners = v; this.stopEvents(); } } public void removeCallObserver(CallObserver observer) { callObservers.remove(observer); this.stopEvents(); } boolean removeConnection(FreeConnection c) { return connections.removeElement(new ConnectionHolder(c)); } /** * Remove an old-style observer from the Address. * If this is the last listener or observer on the address, free if for garbage collection if soft * domain management is used. **/ public void removeObserver(AddressObserver observer) { if (observers.removeObserver(observer)) { sendAddrObservationEndedEv(observer); // check if we no longer need protection this.unProtect(); } } /** * Forward the event off to all observers and listeners * Creation date: (2000-02-14 15:06:59) * @author: Richard Deadman * @param ev The event to forward */ public void send(FreeAddressEvent ev) { // send to observers this.sendToObservers(ev); // send to listeners Iterator<AddressListener> it = this.addressListeners.iterator(); while (it.hasNext()) { AddressListener al = (AddressListener)it.next(); if (ev.getID() == AddressEvent.ADDRESS_EVENT_TRANSMISSION_ENDED) al.addressListenerEnded(ev); } } /** * this is an atypical send method - in that it only fires on a single * listener, not all of them. */ protected void sendAddrObservationEndedEv(AddressObserver ao){ AddrObservationEndedEv e[] = new AddrObservationEndedEv[1]; e[0] = new FreeAddrObsEndedEv(Ev.CAUSE_NORMAL, // cause Ev.META_UNKNOWN, // what is the meta-event false, // is metaevent this); // FreeAddress ao.addressChangedEvent(e); } /** * Send PrivateData to my low-level object for processing. */ public java.lang.Object sendPrivateData(java.lang.Object data) { return ((GenericProvider)this.getProvider()).getRaw().sendPrivateData(null, this.getName(), null, data); } /** * Forward the event off to all observers. * Creation date: (2000-08-09 15:06:59) * @author: Richard Deadman * @param ev The event to forward */ void sendToObservers(FreeAddressEvent ev) { // send to observers FreeAddressEvent[] evs = {ev}; this.observers.sendEvents(evs); } /** * Note if the Address is in the provider's domain * Creation date: (2000-06-22 9:44:13) * @author: Richard Deadman * @param newLocal true if a local Address, otherwise it represents an external participant in the call. */ private void setLocal(boolean newLocal) { this.local = newLocal; } void setName(String newName) { name = newName; } /** * Set PrivateData to be used in the next low-level command. */ public void setPrivateData(java.lang.Object data) { ((GenericProvider)this.getProvider()).getRaw().setPrivateData(null, this.getName(), null, data); } void setProvider(javax.telephony.Provider newProvider) { provider = newProvider; } /** * Note the set of Terminal names associated with the Address. * Creation date: (2000-06-21 11:32:48) * @author: Richard Deadman * @param termNames An array or weak pointers (names) of associated Terminals. */ void setTerminalData(TermData[] termData) { Set<TermData> terms = this.terminals; if (terms == null) { synchronized (this) { // now double check if ((terms = this.terminals) == null) { terms = this.terminals = new HashSet<TermData>(); } } } int size = termData.length; for (int i = 0; i < size; i++) { terms.add(termData[i]); } } /** * Called after Call Observers or Listeners are attached to the Address. * This will determine the current throttle state of the raw provider and update it * if necessary. * Creation date: (2000-05-04 15:23:56) * @author: Richard Deadman */ private synchronized void startEvents() throws ResourceUnavailableException { GenericProvider prov = null; if (!this.reporting) { if ((prov = (GenericProvider)this.getProvider()).getRawCapabilities().throttle) { try { prov.getRaw().reportCallsOnAddress(this.getName(), true); } catch (InvalidArgumentException iae) { // logic error! throw new RuntimeException("Error setting address reporting"); } } this.reporting = true; } } /** * Called after Call Observers or Listeners are detatched from the Address. * This will determine the current throttle state of the raw provider and update it * if necessary. * Creation date: (2000-05-04 15:23:56) * @author: Richard Deadman */ private synchronized void stopEvents() { GenericProvider prov = null; if (this.reporting && this.callListeners.size() == 0 && this.callObservers.size() == 0) { if ((prov = (GenericProvider)this.getProvider()).getRawCapabilities().throttle) { try { prov.getRaw().reportCallsOnAddress(this.getName(), false); } catch (InvalidArgumentException iae) { // logic error! throw new RuntimeException("Error clearing address reporting"); } catch (ResourceUnavailableException rue) { // eat it } ; } this.reporting = false; } } /** * Unprotect this address from garbage collection if dynamic Address memory is supported **/ private void unProtect() { // test if we just removed the last observer if (!isObserved()) { ((GenericProvider)this.getProvider()).getDomainMgr().unProtect(this); } } /** * Describe myself */ public String toString() { return "Address: " + this.getName(); } }