org.semispace.SemiSpace.java Source code

Java tutorial

Introduction

Here is the source code for org.semispace.SemiSpace.java

Source

/*
 * ============================================================================
 *
 *  File:     SemiSpace.java
 *----------------------------------------------------------------------------
 *
 * Copyright 2008 Erlend Nossum
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 *
 *  Description:  See javadoc below
 *
 *  Created:      23. des.. 2007
 * ============================================================================ 
 */

package org.semispace;

import org.json.simple.JSONObject;

import org.semispace.admin.SemiSpaceAdmin;
import org.semispace.api.ISemiSpace;
import org.semispace.api.ISemiSpaceAdmin;
import org.semispace.api.ISemiEventListener;
import org.semispace.api.ISemiSpaceTuple;
import org.semispace.api.ITupleFields;
import org.semispace.event.SemiAvailabilityEvent;
import org.semispace.event.SemiEvent;
import org.semispace.event.SemiExpirationEvent;
import org.semispace.event.SemiRenewalEvent;
import org.semispace.event.SemiTakenEvent;
import org.semispace.exception.SemiSpaceInternalException;
import org.semispace.exception.SemiSpaceObjectException;
import org.semispace.exception.SemiSpaceUsageException;
import org.topicquests.util.LoggingPlatform;
import org.topicquests.util.Tracer;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;

/**
 * A tuple space implementation which can be distributed with terracotta. This is
 * the main class from which the SemiSpace interface is obtained.
 */
public class SemiSpace implements ISemiSpace {
    private LoggingPlatform log = LoggingPlatform.getLiveInstance();
    private static final String ADMIN_GROUP_IS_FLAGGED = "adminGroupIsFlagged";

    public static final long ONE_DAY = 86400 * 1000;

    private static SemiSpace instance = null;

    private long listenerId = 0;

    private TupleCollection elements = null;

    private transient Map<Long, ListenerHolder> listeners;

    private transient ISemiSpaceAdmin admin;

    private transient Map<String, Field[]> classFieldMap = new WeakHashMap<String, Field[]>();

    private SemiSpaceStatistics statistics;

    private EventDistributor eventDistributor = EventDistributor.getInstance();

    /**
     * Tuple for sanity check of stored class. It should not be an inner class.
     */
    private Set<String> checkedClassSet = new HashSet<String>();

    /**
     * 
     * @param spaceName
     */
    public SemiSpace(String spaceName) {

        elements = TupleCollection.retrieveContainer();
        listeners = new ConcurrentHashMap<Long, ListenerHolder>();
        statistics = new SemiSpaceStatistics();
        setAdmin(new SemiSpaceAdmin(this));
        if (admin.hasBeenInitialized()) {
            admin.performInitialization();
        }
        instance = this;
    }

    public static synchronized ISemiSpace retrieveSpace() {
        return instance;
    }

    /**
     * None of the parameters can be null
     * 
     * @return Returning null if something went wrong or was wrong, a registration object otherwise.
     * @see org.semispace.api.ISemiSpace#notify(ISemiSpaceTuple, ISemiEventListener, long)  
     */
    @Override
    public SemiEventRegistration notify(ISemiSpaceTuple tmpl, ISemiEventListener listener, long duration) {
        if (tmpl == null) {
            log.logDebug("Not registering notification on null object.");
            return null;
        }
        //        Map<String, String> searchProps = retrievePropertiesFromObject(tmpl);
        //        SemiEventRegistration registration = notify(searchProps, listener, duration);
        SemiEventRegistration registration = notify(tmpl, listener, duration);
        return registration;
    }

    /**
     * Basically the same as the notify method demanded by the interface, except that it accepts search properties
     * directly. Used from the web services class. None of the parameters can be null
     * 
     * @return Returning null if something went wrong or was wrong, a registration object otherwise.
     * /
    public SemiEventRegistration notify(Map<String, String> searchProps, ISemiEventListener listener, long duration) {
    if (listener == null) {
        log.logDebug("Not allowing listener to be null.");
        return null;
    }
    if (searchProps == null) {
        log.logDebug("Not allowing search props to be null");
        return null;
    }
    if (duration <= 0) {
        log.logDebug("Not registering notification when duration is <= 0. It was " + duration);
        return null;
    }
        
    ListenerHolder holder = null;
    listenerId++;
    holder = new ListenerHolder(listenerId, listener, duration + admin.calculateTime(),
            searchProps);
    if ( listeners.put(Long.valueOf(holder.getId()), holder) != null ) {
        throw new SemiSpaceInternalException("Internal assertion error. Listener map already had element with id "+holder.getId());
    }
    statistics.increaseNumberOfListeners();
    SemiLease lease = new ListenerLease(holder, this);
    SemiEventRegistration eventRegistration = new SemiEventRegistration(holder.getId(), lease);
    return eventRegistration;
    }
        
    /**
     * Distributed notification method.
     */
    public void notifyListeners(DistributedEvent distributedEvent) {
        final List<ISemiEventListener> toNotify = new ArrayList<ISemiEventListener>();
        ListenerHolder[] listenerArray = listeners.values().toArray(new ListenerHolder[0]);
        Arrays.sort(listenerArray, new ShortestTtlComparator());
        for (ListenerHolder listener : listenerArray) {
            if (listener.getLiveUntil() < admin.calculateTime()) {

                cancelListener(listener);

            } else if (hasSubSet(distributedEvent.getEntrySet(), listener.getSearchMap(), false, null, null)) {
                ISemiEventListener notifyMe = listener.getListener();
                toNotify.add(notifyMe);
            }
        }
        final SemiEvent event = distributedEvent.getEvent();
        for (ISemiEventListener notify : toNotify) {
            try {
                notify.notify(event);
            } catch (ClassCastException ignored) {
                // Sadly enough, I need to ignore this due to type erasure.
            }
        }

        admin.notifyAboutEvent(distributedEvent);
    }

    /**
     * Notice that the lease time is the time in milliseconds the element is wants to live, <b>not</b> the system time
     * plus the time to live.
     * 
     * @return Either the resulting lease or null if an error
     */
    @Override
    public SemiLease write(final ISemiSpaceTuple entry, final long leaseTimeMs) {
        if (entry == null) {
            return null;
        }
        //       log.logDebug( "WRITE: "+entry.getJSON());

        //System.out.println("SemiSpace.write-1 "+entry);
        WrappedInternalWriter write = new WrappedInternalWriter(entry, leaseTimeMs);

        ExecutorService thd = admin.getThreadPool();
        //System.out.println("SemiSpace.write-2 "+thd.isShutdown()+" "+thd.isTerminated());

        Future<?> future = thd.submit(write);
        Exception exception = null;
        try {
            future.get();
        } catch (CancellationException e) {
            log.logError("Got exception", e);
            exception = e;
        } catch (InterruptedException e) {
            log.logError("Got exception", e);
            exception = e;
        } catch (ExecutionException e) {
            log.logError("Got exception", e);
            exception = e;
        }
        //System.out.println("SemiSpace.write-3 "+write.getException());

        if (write.getException() != null || exception != null) {
            String error = " Writing object (of type " + entry.getClass().getName()
                    + ") to space gave exception. XML version: " /*+ objectToXml(entry)*/;
            if (write.getException() != null) {
                exception = write.getException();
            }
            throw new SemiSpaceObjectException(error, exception);
        }
        //System.out.println("SemiSpace.write-4 "+write.getLease());
        return write.getLease();
    }

    private SemiLease writeInternally(ISemiSpaceTuple entry, long leaseTimeMs) {
        String tag = entry.getTag();
        Map<String, Object> searchMap = entry.getSearchMap();
        //System.out.println("WriteInternally "+tag+" "+searchMap);
        return writeToElements(tag, leaseTimeMs, searchMap);
    }

    /**
     * This method is public for the benefit of the web services, which shortcuts the writing process.
     * All values are expected to be non-null and valid upon entry.
     */
    public SemiLease writeToElements(String tag, long leaseTimeMs, Map<String, Object> searchMap) {
        Tuple holder = null;
        if (!checkedClassSet.contains(tag)) {
            checkedClassSet.add(tag);
            //        if ( json.contains("<outer-class>")) {
            //            log.logDebug("It seems that "+tag+" is an inner class. This is DISCOURAGED as it WILL serialize the outer " +
            //                   "class as well. If you did not intend this, note that what you store MAY be significantly larger than you " +
            //                   "expected. This warning is printed once for each class type.");
            //       }
        }
        // Need to add holder within lock. This indicates that TupleCollection has some thread safety issues
        holder = elements.addHolder(null, admin.calculateTime() + leaseTimeMs, tag, searchMap);
        //System.out.println("writeToElements-1 "+holder.getId());
        //System.out.println("writeToElements-2 "+holder.getSearchMap());
        SemiLease lease = new ElementLease(holder, this);
        statistics.increaseWrite();

        SemiAvailabilityEvent semiEvent = new SemiAvailabilityEvent(holder.getId());
        distributeEvent(new DistributedEvent(holder.getTag(), semiEvent, holder.getSearchMap()));

        return lease;
    }

    private void distributeEvent(final DistributedEvent distributedEvent) {
        final Runnable distRunnable = new Runnable() {
            @Override
            public void run() {
                eventDistributor.distributeEvent(distributedEvent);
            }
        };
        if (!getAdmin().getThreadPool().isShutdown()) {
            try {
                admin.getThreadPool().execute(distRunnable);
            } catch (RejectedExecutionException e) {
                log.logError("Could not schedule notification", e);
            }
        } else {
            log.logDebug("Thread pool is shut down, not relaying event");
        }
    }

    @Override
    public ISemiSpaceTuple read(ISemiSpaceTuple tmpl, long timeout) {
        //       log.logDebug( "READ-: "+tmpl.getJSON());
        ISemiSpaceTuple found = null;
        if (tmpl != null) {
            found = findOrWaitLeaseForTemplate(getPropertiesForObject(tmpl), timeout, false);
        }
        if (found != null) {
            //          System.out.println("READ Found "+found.getId());
            log.logDebug("READ+: " + found);
        } else {
            //          System.out.println("READ Found "+found);
            log.logDebug("READ+: empty");
        }
        return found; //xmlToObject(found);
    }

    /**
     * Public for the benefit of the webservices interface.
     *
     * @param timeout how long to wait in milliseconds. If timeout is zero or negative, query once. 
     * @param isToTakeTheLease true if the element shall be marked as taken.
     * @return ISemiSpaceTuple version of data, if found, or null
     */
    public ISemiSpaceTuple findOrWaitLeaseForTemplate(Map<String, Object> templateSet, long timeout,
            boolean isToTakeTheLease) {
        final long until = admin.calculateTime() + timeout;
        long systime = admin.calculateTime();
        String tag = (String) templateSet.get("tag");
        //        log.logDebug("findOrWaitLeaseForTemplate "+templateSet+" "+timeout);
        ISemiSpaceTuple found = null;
        long subtract = 0;
        do {
            final long duration = timeout - subtract;
            if (isToTakeTheLease) {
                statistics.increaseBlockingTake();
            } else {
                statistics.increaseBlockingRead();
            }

            found = findLeaseForTemplate(templateSet, isToTakeTheLease);

            if (found == null && duration > 0) {
                elements.waitHolder(tag, duration);
            }
            if (isToTakeTheLease) {
                statistics.decreaseBlockingTake();
            } else {
                statistics.decreaseBlockingRead();
            }

            final long now = getAdmin().calculateTime();
            subtract += now - systime;
            systime = now;
        } while (found == null && systime < until);
        return found;
    }

    @Override
    public ISemiSpaceTuple readIfExists(ISemiSpaceTuple tmpl) {
        return read(tmpl, 0);
    }

    /**
     * 
     * @return ISemiSpaceTuple version of found object
     */
    private ISemiSpaceTuple findLeaseForTemplate(Map<String, Object> templateSet, boolean isToTakeTheLease) {
        ISemiSpaceTuple found = null;

        List<ISemiSpaceTuple> toEvict = new ArrayList<ISemiSpaceTuple>();
        String tag = (String) templateSet.get("tag");

        // Read all elements until element is found. Side effect is to generate eviction list.
        if (tag == null) {
            throw new SemiSpaceObjectException("Did not expect classname to be null");
        }
        //        log.logDebug( "findLeaseForTemplate- "+templateSet);
        TupleCollectionById next = elements.next(tag);
        if (next != null) {
            Iterator<ISemiSpaceTuple> it = next.iterator();
            while (found == null && it.hasNext()) {
                ISemiSpaceTuple elem = it.next();
                //       System.out.println("FINDING ON "+elem.getId());
                //       log.logDebug( "FINDING ON "+elem.getId());
                //see if it's running out of time
                if (elem.getLiveUntil() < admin.calculateTime()) {
                    toEvict.add(elem);
                    elem = null;
                }
                if (elem != null && hasSubSet(elem.getSearchMap().entrySet(), templateSet, isToTakeTheLease,
                        elem.getSearchMap(), elem)) {
                    found = elem;
                }
            }
        }

        for (ISemiSpaceTuple evict : toEvict) {
            if (!cancelElement(Long.valueOf(evict.getId()), false, evict.getTag())) {
                log.logDebug("Element with id " + evict.getId()
                        + " should exist in most cases. This time, it is probably missing as it belongs to a timed out query.");
            }
        }
        boolean needToRetake = false;
        //        log.logDebug("findLeaseForTemplate found "+found);
        if (found != null) {
            if (isToTakeTheLease && !cancelElement(Long.valueOf(found.getId()), isToTakeTheLease, found.getTag())) {
                log.logDebug("Element with id " + found.getId() + " ceased to exist during take. "
                        + "This is not an error; Just an indication of a busy space. ");
                found = null;
                needToRetake = true;
            }
        }

        if (needToRetake) {
            // As element ceased to exist during take, I need to try again. The chances of this is rather slim.
            // Nevertheless, this is needed as the query might have zero in timeout.
            return findLeaseForTemplate(templateSet, isToTakeTheLease);

        } else if (found != null) {
            if (isToTakeTheLease) {
                statistics.increaseTake();
            } else {
                statistics.increaseRead();
            }
        } else {
            if (isToTakeTheLease) {
                statistics.increaseMissedTake();
            } else {
                statistics.increaseMissedRead();
            }
        }
        if (found != null) {
            return found;
        }
        return null;
    }

    /**
     * Used for retrieving element with basis in id
     * @return Element with given holder id, or null if not found (or expired
     */
    public ISemiSpaceTuple readHolderById(long hId) {
        ISemiSpaceTuple result = null;
        result = elements.readHolderWithId(hId);
        return result;
    }

    /**
     * <p>If this is a read (not a take), then we must confirm that the agent's name
     * is not already in the entry</p>
     * <p>Note that this routine uses <code>Collections.containsAll</code> which means
     * that the {@link ITuple} in question must contain all of the fields and values
     * found in <code>templateSubSet</code></p>
     * @param containerEntrySet
     * @param templateSubSet
     * @param isToTakeTheLease 
     * @param searchMap TODO
     * @param theTuple TODO
     * @return
     */
    private boolean hasSubSet(Set<Entry<String, Object>> containerEntrySet, Map<String, Object> templateSubSet,
            boolean isToTakeTheLease, Map<String, Object> searchMap, ISemiSpaceTuple theTuple) {
        if (templateSubSet == null) {
            throw new SemiSpaceUsageException("Did not expect template sub set to be null");
        }
        //HUGE ISSUE: as this code is written, the templates include an ID value
        // which can NEVER be the same as a given tuple.
        templateSubSet.remove(ITupleFields.ID);
        //searchMap is from the tuple itself. In theory, if the tuple is read by an agent,
        //then that agent's name should be in it on the second pass.
        //        log.logDebug("HASSUBSET-0 "+searchMap + " ||| "+templateSubSet);
        //NOTE: template has a String individual agentName whereas tuples
        // accumulate agentName in lists
        String agentName = (String) templateSubSet.get(ITupleFields.AGENT_NAME);
        Set<Entry<String, Object>> templateEntrySet = templateSubSet.entrySet();
        //        log.logDebug("HASSUBSET-1 "+templateEntrySet+" "+agentName+" "+isToTakeTheLease);
        boolean result = false;
        if (!isToTakeTheLease && agentName != null) {
            //has this agent seen this tuple before?
            Object o = searchMap.get(ITupleFields.AGENT_NAME);
            //          log.logDebug("HASSUBSET-2 "+o);
            if (o != null) {
                if (o instanceof String)
                    result = ((String) o).equals(agentName);
                else
                    result = ((List) o).contains(agentName);
            }

        }
        //        log.logDebug("HASSUBSET-3 "+result);
        //note that "true" means this agent has seen this tuple before
        //so just leave with a "not found" result
        if (result)
            return false;
        //The point here is that agentName is not a content field and, if present
        agentName = (String) templateSubSet.remove(ITupleFields.AGENT_NAME);
        //haven't seen it, is it the one?
        result = containerEntrySet.containsAll(templateEntrySet);
        //MUST PUT IT BACK IN THE TEMPLATE since the template is used in iterations
        if (agentName != null)
            templateSubSet.put(ITupleFields.AGENT_NAME, agentName);
        //        log.logDebug("HASSUBSET-4 "+result+" "+containerEntrySet+" "+templateEntrySet);
        if (result) {
            //We found this tuple, so tell it that "agentName" was here!
            //THIS IS COMPLEX!
            // Just adding the agent name back here doesn't solve the problem
            // since the next time the agent asks, the tuple will be fetched
            // back from the queue (until it is times out or is taken) and the situation
            // remains the same.
            if (theTuple != null)
                theTuple.addAgentName(agentName);
        }
        return result;
    }

    @Override
    public ISemiSpaceTuple take(ISemiSpaceTuple tmpl, long timeout) {
        ISemiSpaceTuple found = null;
        if (tmpl != null) {
            found = findOrWaitLeaseForTemplate(getPropertiesForObject(tmpl), timeout, true);
            //           log.logDebug( "TAKE-: "+tmpl.getJSON());
        }
        if (found != null)
            log.logDebug("TAKE+: " + found);
        return found; //xmlToObject(found);
    }

    @Override
    public ISemiSpaceTuple takeIfExists(ISemiSpaceTuple tmpl) {
        return take(tmpl, 0);
    }

    /*
     * Convert some ISemiSpaceTuple to its XML string
        private String objectToXml(Object obj) {
    StringWriter writer = new StringWriter();
    xStream.marshal(obj, new CompactWriter(writer));
    return writer.toString();
        }
    */
    private ISemiSpaceTuple jsonToTuple(String json) {
        return null; //TODO
    }

    /*
    private Object xmlToObject(String xml) {
    if (xml == null || "".equals(xml)) {
        return null;
    }
    Object result = null;
    try {
        result = xStream.fromXML(xml);
    } catch (Exception e) {
        // Not sure if masking exception is the most correct way of dealing with it.
        log.logError("Got exception unmarshalling. Not throwing the exception up, but rather returning null. "
                + "This is as the cause may be a change in the object which is sent over. "
                + "The XML was read as\n" + xml, e);
    }
    return result;
    }
    */
    private static class PreprocessedTemplate implements ISemiSpaceTuple {
        private ISemiSpaceTuple object; //TODO should be ISemiSpaceTuple
        private String tag = "preprocessedtemplate";
        private Map<String, Object> cachedSet;
        private long id = 5;
        private long liveUntil = Long.MAX_VALUE; // default value

        public PreprocessedTemplate(ISemiSpaceTuple object, Map<String, Object> cachedSet) {
            //         System.out.println("PREPROCESSED TEMPLATE "+object);
            this.object = object;
            this.cachedSet = cachedSet;
            cachedSet.put("id", new Long(this.id));
        }

        public Map<String, Object> getCachedSet() {
            return cachedSet;
        }

        public void setCachedSet(Map<String, Object> cachedSet) {
            this.cachedSet = cachedSet;
        }

        public void setObject(ISemiSpaceTuple object) {
            this.object = object;
        }

        @Override
        public String getTag() {
            return tag;
        }

        @Override
        public Map<String, Object> getSearchMap() {
            return cachedSet;
        }

        @Override
        public long getLiveUntil() {
            // TODO Auto-generated method stub
            return 0;
        }

        @Override
        public void setTag(String newTag) {
            tag = newTag;
            cachedSet.put("tag", newTag);
        }

        @Override
        public void set(String name, Object f) {
            // TODO Auto-generated method stub

        }

        @Override
        public Object get(String name) {
            // TODO Auto-generated method stub
            return null;
        }

        @Override
        public long getId() {
            return this.id;
        }

        @Override
        public String getJSON() {
            JSONObject jobj = new JSONObject(this.cachedSet);
            return jobj.toString();
        }

        @Override
        public synchronized void setLiveUntil(long liveUntil) {
            this.liveUntil = liveUntil;
        }

        @Override
        public void setPriority(Long p) {
            // TODO Auto-generated method stub

        }

        @Override
        public Long getPriority() {
            // TODO Auto-generated method stub
            return null;
        }

        @Override
        public void setId(Long id) {
            // TODO Auto-generated method stub

        }

        @Override
        public void addAgentName(String agentName) {
            // TODO Auto-generated method stub

        }
    }

    /**
     * Create a pre-processed template object that can be used to reduce the amount of
     * work required to match templates during a take.  Applications that take a lot of 
     * objects using the same template instance, a noticeable performance improvement
     * can be had.
     * 
     * @param template The object to preprocess
     * @return A pre-processed object that can be passed to read/take
     */
    public ISemiSpaceTuple processTemplate(ISemiSpaceTuple template) {
        PreprocessedTemplate toReturn = null;
        if (template != null) {
            toReturn = new PreprocessedTemplate(template, template.getSearchMap());
        }
        return toReturn;
    }

    private Map<String, Object> getPropertiesForObject(ISemiSpaceTuple object) {
        if (object instanceof PreprocessedTemplate) {
            return ((PreprocessedTemplate) object).getCachedSet();
        }
        return object.getSearchMap();
    }

    /**
     * Protected for the benefit of junit test(s)
     * 
     * @param examine Non-null object
     * /
    protected Map<String, Object> retrievePropertiesFromObject(ISemiSpaceTuple examine) {
    Map<String, Object> map = examine.getSearchMap();
          fillMapWithPublicFields(examine);
    addGettersToMap(examine, map);
        
    //        if ( examine instanceof InternalQuery ) {
    //            map.put(SemiSpace.ADMIN_GROUP_IS_FLAGGED, "true");
    //        }
    // Need to rename class entry in order to separate on class elements.
    String tag = (String)map.remove("tag"); //TODO ????
    map.put("tag", tag );
    return map;
    }
        
    /**
     * Add an objects getter names and values in a map. Note that all values are converted to strings.
     */
    private void addGettersToMap(ISemiSpaceTuple examine, Map<String, Object> map) {
        final Set<String> getters = new HashSet<String>();
        final Method[] methods = examine.getClass().getMethods();
        final Map<String, Method> keyedMethod = new HashMap<String, Method>();
        final Map<String, String> keyedMethodName = new HashMap<String, String>();
        for (Method method : methods) {
            final String name = method.getName();
            final int parameterLength = method.getTypeParameters().length;
            if (parameterLength == 0 && name.startsWith("get")) {
                // Equalize key to [get][set][X]xx
                String normalized = name.substring(3, 4).toLowerCase() + name.substring(4);
                getters.add(normalized);
                keyedMethod.put(name, method);
                keyedMethodName.put(normalized, name);
                //log.info("Got name "+name+" which was normalized to "+normalized);
            }
        }
        for (String name : getters) {
            try {
                Object value = keyedMethod.get(keyedMethodName.get(name)).invoke(examine, null);
                //log.info(">> want to insert "+name+"="+value);
                if (value != null) {
                    map.put(name, "" + value);
                }
            } catch (IllegalAccessException e) {
                log.logError("Could not access method g" + name + ". Got (masked exception) " + e.getMessage(), e);
            } catch (InvocationTargetException e) {
                log.logError("Could not access method g" + name + ". Got (masked exception) " + e.getMessage(), e);
            }
        }
    }

    /**
     * Create a map and fill it with the public fields from the object, which
     * is the JavaSpace manner.
     */
    private Map<String, Object> fillMapWithPublicFields(ISemiSpaceTuple examine) {
        Field[] fields = classFieldMap.get(examine.getTag());
        if (fields == null) {
            fields = examine.getClass().getFields();
            classFieldMap.put(examine.getClass().getName(), fields);
        }
        Map<String, Object> map = new HashMap<String, Object>();
        for (Field field : fields) {
            try {
                String name = field.getName();
                Object value = field.get(examine);

                if (value != null) {
                    map.put(name, "" + value);
                }
            } catch (IllegalAccessException e) {
                log.logError("Introspection gave exception - which is not re-thrown.", e);
            }
        }
        return map;
    }

    /**
     * Preparing for future injection of admin. Note that you
     * must call initialization <b>yourself</b> after setting the
     * object
     * <p>
     * This is tested in junit test (and under terracotta).
     * </p>
     */
    public void setAdmin(ISemiSpaceAdmin admin) {
        this.admin = admin;
    }

    /**
     * Return admin element
     */
    public ISemiSpaceAdmin getAdmin() {
        return this.admin;
    }

    /**
     * Need to wrap write in own thread in order to make terracotta pick it up.
     */
    protected class WrappedInternalWriter implements Runnable {
        private ISemiSpaceTuple entry;

        private long leaseTimeMs;

        private Exception exception;

        private SemiLease lease;

        public Exception getException() {
            return this.exception;
        }

        public SemiLease getLease() {
            return lease;
        }

        protected WrappedInternalWriter(ISemiSpaceTuple entry, long leaseTimeMs) {
            this.entry = entry;
            this.leaseTimeMs = leaseTimeMs;
        }

        @Override
        @SuppressWarnings("synthetic-access")
        public void run() {
            //          System.out.println("WrappedInternalWriter running "+entry);
            try {
                lease = writeInternally(entry, leaseTimeMs);
            } catch (Exception e) {
                log.logError("Got exception writing object.", e);
                exception = e;
            }
        }
    }

    /**
     * Harvest old elements from diverse listeners. Used from 
     * the periodic harvester and junit tests.
     */
    public void harvest() {

        for (ListenerHolder listener : listeners.values()) {
            if (listener.getLiveUntil() < admin.calculateTime()) {
                cancelListener(listener);

            }
        }
        List<ISemiSpaceTuple> beforeEvict = new ArrayList<ISemiSpaceTuple>();

        String[] groups = elements.retrieveGroupNames();
        for (String group : groups) {
            int evictSize = beforeEvict.size();
            TupleCollectionById hc = elements.next(group);
            for (ISemiSpaceTuple elem : hc) {
                if (elem.getLiveUntil() < admin.calculateTime()) {
                    beforeEvict.add(elem);
                }
            }
            long afterSize = beforeEvict.size() - evictSize;
            if (afterSize > 0) {
                List<Long> ids = new ArrayList<Long>();
                for (ISemiSpaceTuple evict : beforeEvict) {
                    ids.add(Long.valueOf(evict.getId()));
                }
                String moreInfo = "";
                if (ids.size() < 30) {
                    Collections.sort(ids);
                    moreInfo = "Ids: " + ids;
                }
                //               log.logDebug("Testing group "+group+" gave "+afterSize+" element(s) to evict. "+moreInfo);
            }
        }
        for (ISemiSpaceTuple evict : beforeEvict) {
            cancelElement(Long.valueOf(evict.getId()), false, evict.getTag());
        }
    }

    /**
     * Return the number of elements in the space. Notice that this may report old elements that have not been purged
     * yet.
     */
    public int numberOfSpaceElements() {
        int size;
        size = elements.size();
        return size;
    }

    /** Need present statistics here due to spring JMX configuration. */
    public int numberOfBlockingRead() {
        return statistics.getBlockingRead();
    }

    /** Need present statistics here due to spring JMX configuration. */
    public int numberOfBlockingTake() {
        return statistics.getBlockingTake();
    }

    /** Need present statistics here due to spring JMX configuration. */
    public int numberOfMissedRead() {
        return statistics.getMissedRead();
    }

    /** Need present statistics here due to spring JMX configuration. */
    public int numberOfMissedTake() {
        return statistics.getMissedTake();
    }

    /** Need present statistics here due to spring JMX configuration. */
    public int numberOfNumberOfListeners() {
        return statistics.getNumberOfListeners();
    }

    /** Need present statistics here due to spring JMX configuration. */
    public int numberOfRead() {
        return statistics.getRead();
    }

    /** Need present statistics here due to spring JMX configuration. */
    public int numberOfTake() {
        return statistics.getTake();
    }

    /** Need present statistics here due to spring JMX configuration. */
    public int numberOfWrite() {
        return statistics.getWrite();
    }

    /**
     * For the benefit of junit test(s) - defensively copied statistics
     */
    protected SemiSpaceStatistics getStatistics() {
        SemiSpaceStatistics stats;
        // Defensive copied statistics
        stats = (SemiSpaceStatistics) null; // TODO xmlToObject(objectToXml(statistics));
        return stats;
    }

    protected boolean cancelListener(ListenerHolder holder) {
        boolean success = false;

        ListenerHolder listener = listeners.remove(Long.valueOf(holder.getId()));
        if (listener != null) {
            statistics.decreaseNumberOfListeners();
            success = true;
        }

        return success;
    }

    protected boolean renewListener(ListenerHolder holder, long duration) {
        boolean success = false;

        ListenerHolder listener = listeners.get(Long.valueOf(holder.getId()));
        if (listener != null) {
            listener.setLiveUntil(duration + admin.calculateTime());
            // Need to re-get due in order to be certain of liveness.
            success = listeners.get(Long.valueOf(holder.getId())) != null;
        }
        return success;
    }

    /**
     * @param isTake true if reason for the cancellation is a take.
     */
    protected boolean cancelElement(Long id, boolean isTake, String className) {
        boolean success = false;

        ISemiSpaceTuple elem = elements.removeHolderById(id.longValue(), className);
        if (elem != null) {
            if (elem.getId() != id.longValue()) {
                throw new SemiSpaceInternalException("Sanity problem. Removed " + id.longValue()
                        + " and got back element with id " + elem.getId());
            }
            success = true;
            SemiEvent semiEvent = null;
            if (isTake) {
                semiEvent = new SemiTakenEvent(elem.getId());
            } else {
                semiEvent = new SemiExpirationEvent(elem.getId());
            }
            //log.logDebug("Notifying about "+(isTake?"take":"expiration")+" of element with id "+semiEvent.getId());
            distributeEvent(new DistributedEvent(elem.getTag(), semiEvent, elem.getSearchMap()));

        }

        return success;
    }

    /**
     * @return true if the object actually was renewed. (I.e. it exists and got a new timeout.)
     */
    protected boolean renewElement(ISemiSpaceTuple holder, long duration) {
        boolean success = false;
        ISemiSpaceTuple elem = elements.findById(holder.getId(), holder.getTag());
        if (elem != null) {
            elem.setLiveUntil(duration + admin.calculateTime());
            success = true;
            distributeEvent(new DistributedEvent(elem.getTag(),
                    new SemiRenewalEvent(elem.getId(), elem.getLiveUntil()), elem.getSearchMap()));
        }

        return success;
    }

    /**
     * @see TupleCollection#findAllHolderIds
     */
    public Long[] findAllHolderIds() {
        Long[] result = null;
        result = elements.findAllHolderIds();
        return result;
    }

    /**
     * Exposing xstream instance in order to allow outside manipulation of aliases and classloader affiliation.
     * @return The xstream instance used.
     */
    //   public XStream getXStream() {
    //       return xStream;
    //   }

    private static class ShortestTtlComparator implements Comparator<ListenerHolder>, Serializable {
        @Override
        public int compare(ListenerHolder o1, ListenerHolder o2) {
            if (o1 == null || o2 == null) {
                throw new SemiSpaceUsageException("Did not expect any null values for listenerHolder.");
            }
            return (int) (o1.getLiveUntil() - o2.getLiveUntil());
        }
    }
}