freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [5/5] incubator-freemarker git commit: Moved debug feature into a single package.
Date Wed, 01 Mar 2017 18:39:09 GMT
Moved debug feature into a single package.


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/6939fd07
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/6939fd07
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/6939fd07

Branch: refs/heads/3
Commit: 6939fd075a3188594d94582fac892b032eb92d2d
Parents: 64ca9c2
Author: ddekany <ddekany@apache.org>
Authored: Wed Mar 1 19:38:54 2017 +0100
Committer: ddekany <ddekany@apache.org>
Committed: Wed Mar 1 19:38:54 2017 +0100

----------------------------------------------------------------------
 .../apache/freemarker/core/ASTDebugBreak.java   |   4 +-
 .../apache/freemarker/core/Configuration.java   |  13 +-
 .../org/apache/freemarker/core/Template.java    |   6 +-
 .../freemarker/core/debug/DebuggerClient.java   |   1 -
 .../freemarker/core/debug/DebuggerServer.java   | 130 +++++++
 .../core/debug/RmiDebugModelImpl.java           | 164 +++++++++
 .../core/debug/RmiDebuggedEnvironmentImpl.java  | 335 ++++++++++++++++++
 .../freemarker/core/debug/RmiDebuggerImpl.java  |  86 +++++
 .../core/debug/RmiDebuggerListenerImpl.java     |  67 ++++
 .../core/debug/RmiDebuggerService.java          | 307 +++++++++++++++++
 .../apache/freemarker/core/debug/SoftCache.java |  89 +++++
 .../freemarker/core/debug/_DebuggerService.java |  93 +++++
 .../core/debug/impl/DebuggerServer.java         | 130 -------
 .../core/debug/impl/DebuggerService.java        |  93 -----
 .../core/debug/impl/RmiDebugModelImpl.java      | 165 ---------
 .../debug/impl/RmiDebuggedEnvironmentImpl.java  | 337 -------------------
 .../core/debug/impl/RmiDebuggerImpl.java        |  90 -----
 .../debug/impl/RmiDebuggerListenerImpl.java     |  67 ----
 .../core/debug/impl/RmiDebuggerService.java     | 310 -----------------
 .../freemarker/core/debug/impl/SoftCache.java   |  89 -----
 20 files changed, 1288 insertions(+), 1288 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6939fd07/src/main/java/org/apache/freemarker/core/ASTDebugBreak.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTDebugBreak.java b/src/main/java/org/apache/freemarker/core/ASTDebugBreak.java
index b85c10b..fe42f41 100644
--- a/src/main/java/org/apache/freemarker/core/ASTDebugBreak.java
+++ b/src/main/java/org/apache/freemarker/core/ASTDebugBreak.java
@@ -21,7 +21,7 @@ package org.apache.freemarker.core;
 
 import java.io.IOException;
 
-import org.apache.freemarker.core.debug.impl.DebuggerService;
+import org.apache.freemarker.core.debug._DebuggerService;
 
 /**
  * AST node: A debug breakpoint
@@ -34,7 +34,7 @@ class ASTDebugBreak extends ASTElement {
     
     @Override
     protected ASTElement[] accept(Environment env) throws TemplateException, IOException {
-        if (!DebuggerService.suspendEnvironment(
+        if (!_DebuggerService.suspendEnvironment(
                 env, getTemplate().getSourceName(), getChild(0).getBeginLine())) {
             return getChild(0).accept(env);
         } else {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6939fd07/src/main/java/org/apache/freemarker/core/Configuration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/Configuration.java b/src/main/java/org/apache/freemarker/core/Configuration.java
index b85dacc..6b256b6 100644
--- a/src/main/java/org/apache/freemarker/core/Configuration.java
+++ b/src/main/java/org/apache/freemarker/core/Configuration.java
@@ -36,6 +36,7 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Properties;
 import java.util.Set;
+import java.util.TimeZone;
 import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -489,6 +490,11 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
         loadBuiltInSharedVariables();
     }
 
+    @Override
+    public void setTimeZone(TimeZone timeZone) {
+        super.setTimeZone(timeZone);
+    }
+
     private void createTemplateResolver() {
         templateResolver = new DefaultTemplateResolver(
                 null,
@@ -500,7 +506,12 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
         templateResolver.clearTemplateCache(); // for fully BC behavior
         templateResolver.setTemplateUpdateDelayMilliseconds(5000);
     }
-    
+
+    @Override
+    public TemplateExceptionHandler getTemplateExceptionHandler() {
+        return super.getTemplateExceptionHandler();
+    }
+
     private void recreateTemplateResolverWith(
             TemplateLoader loader, CacheStorage storage,
             TemplateLookupStrategy templateLookupStrategy, TemplateNameFormat templateNameFormat,

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6939fd07/src/main/java/org/apache/freemarker/core/Template.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/Template.java b/src/main/java/org/apache/freemarker/core/Template.java
index 87940a8..7b981af 100644
--- a/src/main/java/org/apache/freemarker/core/Template.java
+++ b/src/main/java/org/apache/freemarker/core/Template.java
@@ -37,7 +37,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Vector;
 
-import org.apache.freemarker.core.debug.impl.DebuggerService;
+import org.apache.freemarker.core.debug._DebuggerService;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.TemplateHashModel;
 import org.apache.freemarker.core.model.TemplateModel;
@@ -279,7 +279,7 @@ public class Template extends Configurable {
         // Throws any exception that JavaCC has silently treated as EOF:
         ltbReader.throwFailure();
         
-        DebuggerService.registerTemplate(this);
+        _DebuggerService.registerTemplate(this);
         namespaceURIToPrefixLookup = Collections.unmodifiableMap(namespaceURIToPrefixLookup);
         prefixToNamespaceURILookup = Collections.unmodifiableMap(prefixToNamespaceURILookup);
     }
@@ -318,7 +318,7 @@ public class Template extends Configurable {
         ((ASTStaticText) template.rootElement).replaceText(content);
         template.setEncoding(encoding);
 
-        DebuggerService.registerTemplate(template);
+        _DebuggerService.registerTemplate(template);
 
         return template;
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6939fd07/src/main/java/org/apache/freemarker/core/debug/DebuggerClient.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/debug/DebuggerClient.java b/src/main/java/org/apache/freemarker/core/debug/DebuggerClient.java
index 1aa740d..a4612e2 100644
--- a/src/main/java/org/apache/freemarker/core/debug/DebuggerClient.java
+++ b/src/main/java/org/apache/freemarker/core/debug/DebuggerClient.java
@@ -30,7 +30,6 @@ import java.security.MessageDigest;
 import java.util.Collection;
 import java.util.List;
 
-import org.apache.freemarker.core.debug.impl.RmiDebuggerListenerImpl;
 import org.apache.freemarker.core.util.UndeclaredThrowableException;
 
 /**

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6939fd07/src/main/java/org/apache/freemarker/core/debug/DebuggerServer.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/debug/DebuggerServer.java b/src/main/java/org/apache/freemarker/core/debug/DebuggerServer.java
new file mode 100644
index 0000000..d142d16
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/debug/DebuggerServer.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.freemarker.core.debug;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Random;
+
+import org.apache.freemarker.core._CoreLogs;
+import org.apache.freemarker.core.debug.Debugger;
+import org.apache.freemarker.core.util.UndeclaredThrowableException;
+import org.apache.freemarker.core.util._SecurityUtil;
+import org.slf4j.Logger;
+
+/**
+ */
+class DebuggerServer {
+
+    private static final Logger LOG = _CoreLogs.DEBUG_SERVER;
+    
+    // TODO: Eventually replace with Yarrow    
+    // TODO: Can be extremely slow (on Linux, not enough entropy)
+    private static final Random R = new SecureRandom();
+    
+    private final byte[] password;
+    private final int port;
+    private final Serializable debuggerStub;
+    private boolean stop = false;
+    private ServerSocket serverSocket;
+    
+    public DebuggerServer(Serializable debuggerStub) {
+        port = _SecurityUtil.getSystemProperty("org.apache.freemarker.core.debug.port", Debugger.DEFAULT_PORT).intValue();
+        try {
+            password = _SecurityUtil.getSystemProperty("org.apache.freemarker.core.debug.password", "").getBytes("UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+        this.debuggerStub = debuggerStub;
+    }
+    
+    public void start() {
+        new Thread(new Runnable()
+        {
+            @Override
+            public void run() {
+                startInternal();
+            }
+        }, "FreeMarker Debugger Server Acceptor").start();
+    }
+    
+    private void startInternal() {
+        try {
+            serverSocket = new ServerSocket(port);
+            while (!stop) {
+                Socket s = serverSocket.accept();
+                new Thread(new DebuggerAuthProtocol(s)).start();
+            }
+        } catch (IOException e) {
+            LOG.error("Debugger server shut down.", e);
+        }
+    }
+    
+    private class DebuggerAuthProtocol implements Runnable {
+        private final Socket s;
+        
+        DebuggerAuthProtocol(Socket s) {
+            this.s = s;
+        }
+        
+        @Override
+        public void run() {
+            try {
+                ObjectOutputStream out = new ObjectOutputStream(s.getOutputStream());
+                ObjectInputStream in = new ObjectInputStream(s.getInputStream());
+                byte[] challenge = new byte[512];
+                R.nextBytes(challenge);
+                out.writeInt(220); // protocol version
+                out.writeObject(challenge);
+                MessageDigest md = MessageDigest.getInstance("SHA");
+                md.update(password);
+                md.update(challenge);
+                byte[] response = (byte[]) in.readObject();
+                if (Arrays.equals(response, md.digest())) {
+                    out.writeObject(debuggerStub);
+                } else {
+                    out.writeObject(null);
+                }
+            } catch (Exception e) {
+                LOG.warn("Connection to {} abruptly broke", s.getInetAddress().getHostAddress(), e);
+            }
+        }
+
+    }
+
+    public void stop() {
+        stop = true;
+        if (serverSocket != null) {
+            try {
+                serverSocket.close();
+            } catch (IOException e) {
+                LOG.error("Unable to close server socket.", e);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6939fd07/src/main/java/org/apache/freemarker/core/debug/RmiDebugModelImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/debug/RmiDebugModelImpl.java b/src/main/java/org/apache/freemarker/core/debug/RmiDebugModelImpl.java
new file mode 100644
index 0000000..bb11db3
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/debug/RmiDebugModelImpl.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.freemarker.core.debug;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx;
+import org.apache.freemarker.core.model.TemplateMethodModel;
+import org.apache.freemarker.core.model.TemplateMethodModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.model.TemplateTransformModel;
+
+/**
+ */
+class RmiDebugModelImpl extends UnicastRemoteObject implements DebugModel {
+    private static final long serialVersionUID = 1L;
+
+    private final TemplateModel model;
+    private final int type;
+    
+    RmiDebugModelImpl(TemplateModel model, int extraTypes) throws RemoteException {
+        super();
+        this.model = model;
+        type = calculateType(model) + extraTypes;
+    }
+
+    private static DebugModel getDebugModel(TemplateModel tm) throws RemoteException {
+        return (DebugModel) RmiDebuggedEnvironmentImpl.getCachedWrapperFor(tm);
+    }
+    @Override
+    public String getAsString() throws TemplateModelException {
+        return ((TemplateScalarModel) model).getAsString();
+    }
+
+    @Override
+    public Number getAsNumber() throws TemplateModelException {
+        return ((TemplateNumberModel) model).getAsNumber();
+    }
+
+    @Override
+    public Date getAsDate() throws TemplateModelException {
+        return ((TemplateDateModel) model).getAsDate();
+    }
+
+    @Override
+    public int getDateType() {
+        return ((TemplateDateModel) model).getDateType();
+    }
+
+    @Override
+    public boolean getAsBoolean() throws TemplateModelException {
+        return ((TemplateBooleanModel) model).getAsBoolean();
+    }
+
+    @Override
+    public int size() throws TemplateModelException {
+        if (model instanceof TemplateSequenceModel) {
+            return ((TemplateSequenceModel) model).size();
+        }
+        return ((TemplateHashModelEx) model).size();
+    }
+
+    @Override
+    public DebugModel get(int index) throws TemplateModelException, RemoteException {
+        return getDebugModel(((TemplateSequenceModel) model).get(index));
+    }
+    
+    @Override
+    public DebugModel[] get(int fromIndex, int toIndex) throws TemplateModelException, RemoteException {
+        DebugModel[] dm = new DebugModel[toIndex - fromIndex];
+        TemplateSequenceModel s = (TemplateSequenceModel) model;
+        for (int i = fromIndex; i < toIndex; i++) {
+            dm[i - fromIndex] = getDebugModel(s.get(i));
+        }
+        return dm;
+    }
+
+    @Override
+    public DebugModel[] getCollection() throws TemplateModelException, RemoteException {
+        List list = new ArrayList();
+        TemplateModelIterator i = ((TemplateCollectionModel) model).iterator();
+        while (i.hasNext()) {
+            list.add(getDebugModel(i.next()));
+        }
+        return (DebugModel[]) list.toArray(new DebugModel[list.size()]);
+    }
+    
+    @Override
+    public DebugModel get(String key) throws TemplateModelException, RemoteException {
+        return getDebugModel(((TemplateHashModel) model).get(key));
+    }
+    
+    @Override
+    public DebugModel[] get(String[] keys) throws TemplateModelException, RemoteException {
+        DebugModel[] dm = new DebugModel[keys.length];
+        TemplateHashModel h = (TemplateHashModel) model;
+        for (int i = 0; i < keys.length; i++) {
+            dm[i] = getDebugModel(h.get(keys[i]));
+        }
+        return dm;
+    }
+
+    @Override
+    public String[] keys() throws TemplateModelException {
+        TemplateHashModelEx h = (TemplateHashModelEx) model;
+        List list = new ArrayList();
+        TemplateModelIterator i = h.keys().iterator();
+        while (i.hasNext()) {
+            list.add(((TemplateScalarModel) i.next()).getAsString());
+        }
+        return (String[]) list.toArray(new String[list.size()]);
+    }
+
+    @Override
+    public int getModelTypes() {
+        return type;
+    }
+    
+    private static int calculateType(TemplateModel model) {
+        int type = 0;
+        if (model instanceof TemplateScalarModel) type += TYPE_SCALAR;
+        if (model instanceof TemplateNumberModel) type += TYPE_NUMBER;
+        if (model instanceof TemplateDateModel) type += TYPE_DATE;
+        if (model instanceof TemplateBooleanModel) type += TYPE_BOOLEAN;
+        if (model instanceof TemplateSequenceModel) type += TYPE_SEQUENCE;
+        if (model instanceof TemplateCollectionModel) type += TYPE_COLLECTION;
+        if (model instanceof TemplateHashModelEx) type += TYPE_HASH_EX;
+        else if (model instanceof TemplateHashModel) type += TYPE_HASH;
+        if (model instanceof TemplateMethodModelEx) type += TYPE_METHOD_EX;
+        else if (model instanceof TemplateMethodModel) type += TYPE_METHOD;
+        if (model instanceof TemplateTransformModel) type += TYPE_TRANSFORM;
+        return type;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6939fd07/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java b/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java
new file mode 100644
index 0000000..3229c81
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java
@@ -0,0 +1,335 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.freemarker.core.debug;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.freemarker.core.Configurable;
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.Template;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.impl.SimpleCollection;
+import org.apache.freemarker.core.model.impl.SimpleScalar;
+import org.apache.freemarker.core.util.UndeclaredThrowableException;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+@SuppressWarnings("serial")
+class RmiDebuggedEnvironmentImpl extends RmiDebugModelImpl implements DebuggedEnvironment {
+
+    private static final SoftCache CACHE = new SoftCache(new IdentityHashMap());
+    private static final Object ID_LOCK = new Object();
+    
+    private static long nextId = 1;
+    private static Set remotes = new HashSet();
+
+    
+    private boolean stopped = false;
+    private final long id;
+    
+    private RmiDebuggedEnvironmentImpl(Environment env) throws RemoteException {
+        super(new DebugEnvironmentModel(env), DebugModel.TYPE_ENVIRONMENT);
+        synchronized (ID_LOCK) {
+            id = nextId++;
+        }
+    }
+
+    static synchronized Object getCachedWrapperFor(Object key)
+    throws RemoteException {
+        Object value = CACHE.get(key);
+        if (value == null) {
+            if (key instanceof TemplateModel) {
+                int extraTypes;
+                if (key instanceof DebugConfigurationModel) {
+                    extraTypes = DebugModel.TYPE_CONFIGURATION;
+                } else if (key instanceof DebugTemplateModel) {
+                    extraTypes = DebugModel.TYPE_TEMPLATE;
+                } else {
+                    extraTypes = 0;
+                }
+                value = new RmiDebugModelImpl((TemplateModel) key, extraTypes);
+            } else if (key instanceof Environment) {
+                value = new RmiDebuggedEnvironmentImpl((Environment) key); 
+            } else if (key instanceof Template) {
+                value = new DebugTemplateModel((Template) key);
+            } else if (key instanceof Configuration) {
+                value = new DebugConfigurationModel((Configuration) key);
+            }
+        }
+        if (value != null) {
+            CACHE.put(key, value);
+        }
+        if (value instanceof Remote) {
+            remotes.add(value);
+        }
+        return value;
+    }
+
+    // TODO See in SuppressFBWarnings
+    @Override
+    @SuppressFBWarnings(value="NN_NAKED_NOTIFY", justification="Will have to be re-desigend; postponed.")
+    public void resume() {
+        synchronized (this) {
+            notify();
+        }
+    }
+
+    @Override
+    public void stop() {
+        stopped = true;
+        resume();
+    }
+
+    @Override
+    public long getId() {
+        return id;
+    }
+    
+    boolean isStopped() {
+        return stopped;
+    }
+    
+    private abstract static class DebugMapModel implements TemplateHashModelEx {
+        @Override
+        public int size() {
+            return keySet().size();
+        }
+
+        @Override
+        public TemplateCollectionModel keys() {
+            return new SimpleCollection(keySet());
+        }
+
+        @Override
+        public TemplateCollectionModel values() throws TemplateModelException {
+            Collection keys = keySet();
+            List list = new ArrayList(keys.size());
+            
+            for (Iterator it = keys.iterator(); it.hasNext(); ) {
+                list.add(get((String) it.next()));
+            }
+            return new SimpleCollection(list);
+        }
+
+        @Override
+        public boolean isEmpty() {
+            return size() == 0;
+        }
+        
+        abstract Collection keySet();
+
+        static List composeList(Collection c1, Collection c2) {
+            List list = new ArrayList(c1);
+            list.addAll(c2);
+            Collections.sort(list);
+            return list;
+        }
+    }
+    
+    private static class DebugConfigurableModel extends DebugMapModel {
+        static final List KEYS = Arrays.asList(
+                Configurable.ARITHMETIC_ENGINE_KEY,
+                Configurable.BOOLEAN_FORMAT_KEY,
+                Configurable.LOCALE_KEY,
+                Configurable.NUMBER_FORMAT_KEY,
+                Configurable.OBJECT_WRAPPER_KEY,
+                Configurable.TEMPLATE_EXCEPTION_HANDLER_KEY);
+
+        final Configurable configurable;
+        
+        DebugConfigurableModel(Configurable configurable) {
+            this.configurable = configurable;
+        }
+        
+        @Override
+        Collection keySet() {
+            return KEYS;
+        }
+        
+        @Override
+        public TemplateModel get(String key) throws TemplateModelException {
+            return null; // TODO
+        }
+
+    }
+    
+    private static class DebugConfigurationModel extends DebugConfigurableModel {
+        private static final List KEYS = composeList(DebugConfigurableModel.KEYS, Collections.singleton("sharedVariables"));
+
+        private TemplateModel sharedVariables = new DebugMapModel()
+        {
+            @Override
+            Collection keySet() {
+                return ((Configuration) configurable).getSharedVariableNames();
+            }
+        
+            @Override
+            public TemplateModel get(String key) {
+                return ((Configuration) configurable).getSharedVariable(key);
+            }
+        };
+        
+        DebugConfigurationModel(Configuration config) {
+            super(config);
+        }
+        
+        @Override
+        Collection keySet() {
+            return KEYS;
+        }
+
+        @Override
+        public TemplateModel get(String key) throws TemplateModelException {
+            if ("sharedVariables".equals(key)) {
+                return sharedVariables; 
+            } else {
+                return super.get(key);
+            }
+        }
+    }
+    
+    private static class DebugTemplateModel extends DebugConfigurableModel {
+        private static final List KEYS = composeList(DebugConfigurableModel.KEYS, 
+            Arrays.asList("configuration", "name"));
+    
+        private final SimpleScalar name;
+
+        DebugTemplateModel(Template template) {
+            super(template);
+            name = new SimpleScalar(template.getName());
+        }
+
+        @Override
+        Collection keySet() {
+            return KEYS;
+        }
+
+        @Override
+        public TemplateModel get(String key) throws TemplateModelException {
+            if ("configuration".equals(key)) {
+                try {
+                    return (TemplateModel) getCachedWrapperFor(((Template) configurable).getConfiguration());
+                } catch (RemoteException e) {
+                    throw new TemplateModelException(e);
+                }
+            }
+            if ("name".equals(key)) {
+                return name;
+            }
+            return super.get(key);
+        }
+    }
+
+    private static class DebugEnvironmentModel extends DebugConfigurableModel {
+        private static final List KEYS = composeList(DebugConfigurableModel.KEYS, 
+            Arrays.asList(
+                    "currentNamespace",
+                    "dataModel",
+                    "globalNamespace",
+                    "knownVariables",
+                    "mainNamespace",
+                    "template"));
+    
+        private TemplateModel knownVariables = new DebugMapModel()
+        {
+            @Override
+            Collection keySet() {
+                try {
+                    return ((Environment) configurable).getKnownVariableNames();
+                } catch (TemplateModelException e) {
+                    throw new UndeclaredThrowableException(e);
+                }
+            }
+        
+            @Override
+            public TemplateModel get(String key) throws TemplateModelException {
+                return ((Environment) configurable).getVariable(key);
+            }
+        };
+         
+        DebugEnvironmentModel(Environment env) {
+            super(env);
+        }
+
+        @Override
+        Collection keySet() {
+            return KEYS;
+        }
+
+        @Override
+        public TemplateModel get(String key) throws TemplateModelException {
+            if ("currentNamespace".equals(key)) {
+                return ((Environment) configurable).getCurrentNamespace();
+            }
+            if ("dataModel".equals(key)) {
+                return ((Environment) configurable).getDataModel();
+            }
+            if ("globalNamespace".equals(key)) {
+                return ((Environment) configurable).getGlobalNamespace();
+            }
+            if ("knownVariables".equals(key)) {
+                return knownVariables;
+            }
+            if ("mainNamespace".equals(key)) {
+                return ((Environment) configurable).getMainNamespace();
+            }
+            if ("mainTemplate".equals(key)) {
+                try {
+                    return (TemplateModel) getCachedWrapperFor(((Environment) configurable).getMainTemplate());
+                } catch (RemoteException e) {
+                    throw new TemplateModelException(e);
+                }
+            }
+            if ("currentTemplate".equals(key)) {
+                try {
+                    return (TemplateModel) getCachedWrapperFor(((Environment) configurable).getCurrentTemplate());
+                } catch (RemoteException e) {
+                    throw new TemplateModelException(e);
+                }
+            }
+            return super.get(key);
+        }
+    }
+
+    public static void cleanup() {
+        for (Iterator i = remotes.iterator(); i.hasNext(); ) {
+            Object remoteObject = i.next();
+            try {
+                UnicastRemoteObject.unexportObject((Remote) remoteObject, true);
+            } catch (Exception e) {
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6939fd07/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerImpl.java b/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerImpl.java
new file mode 100644
index 0000000..ea54e4e
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerImpl.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.freemarker.core.debug;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ */
+class RmiDebuggerImpl
+extends
+    UnicastRemoteObject
+implements
+    Debugger {
+    private static final long serialVersionUID = 1L;
+
+    private final RmiDebuggerService service;
+    
+    protected RmiDebuggerImpl(RmiDebuggerService service) throws RemoteException {
+        this.service = service;
+    }
+
+    @Override
+    public void addBreakpoint(Breakpoint breakpoint) {
+        service.addBreakpoint(breakpoint);
+    }
+
+    @Override
+    public Object addDebuggerListener(DebuggerListener listener) {
+        return service.addDebuggerListener(listener);
+    }
+
+    @Override
+    public List getBreakpoints() {
+        return service.getBreakpointsSpi();
+    }
+
+    @Override
+    public List getBreakpoints(String templateName) {
+        return service.getBreakpointsSpi(templateName);
+    }
+
+    @Override
+    public Collection getSuspendedEnvironments() {
+        return service.getSuspendedEnvironments();
+    }
+
+    @Override
+    public void removeBreakpoint(Breakpoint breakpoint) {
+        service.removeBreakpoint(breakpoint);
+    }
+
+    @Override
+    public void removeDebuggerListener(Object id) {
+        service.removeDebuggerListener(id);
+    }
+
+    @Override
+    public void removeBreakpoints() {
+        service.removeBreakpoints();
+    }
+
+    @Override
+    public void removeBreakpoints(String templateName) {
+        service.removeBreakpoints(templateName);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6939fd07/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerListenerImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerListenerImpl.java b/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerListenerImpl.java
new file mode 100644
index 0000000..7e263d8
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerListenerImpl.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.freemarker.core.debug;
+
+import java.rmi.NoSuchObjectException;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.rmi.server.Unreferenced;
+
+import org.apache.freemarker.core._CoreLogs;
+import org.apache.freemarker.core.debug.DebuggerClient;
+import org.apache.freemarker.core.debug.DebuggerListener;
+import org.apache.freemarker.core.debug.EnvironmentSuspendedEvent;
+import org.slf4j.Logger;
+
+/**
+ * Used by the {@link DebuggerClient} to create local 
+ */
+class RmiDebuggerListenerImpl
+extends
+    UnicastRemoteObject
+implements
+    DebuggerListener, Unreferenced {
+    
+    private static final Logger LOG = _CoreLogs.DEBUG_CLIENT;
+    
+    private static final long serialVersionUID = 1L;
+
+    private final DebuggerListener listener;
+
+    @Override
+    public void unreferenced() {
+        try {
+            UnicastRemoteObject.unexportObject(this, false);
+        } catch (NoSuchObjectException e) {
+            LOG.warn("Failed to unexport RMI debugger listener", e);
+        }
+    }
+    
+    public RmiDebuggerListenerImpl(DebuggerListener listener) 
+    throws RemoteException {
+        this.listener = listener;
+    }
+
+    @Override
+    public void environmentSuspended(EnvironmentSuspendedEvent e) 
+    throws RemoteException {
+        listener.environmentSuspended(e);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6939fd07/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerService.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerService.java b/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerService.java
new file mode 100644
index 0000000..afa0ec0
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerService.java
@@ -0,0 +1,307 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.freemarker.core.debug;
+
+import java.io.Serializable;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.rmi.RemoteException;
+import java.rmi.server.RemoteObject;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.Template;
+import org.apache.freemarker.core._Debug;
+import org.apache.freemarker.core.util.UndeclaredThrowableException;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * @version $Id
+ */
+class RmiDebuggerService
+extends
+        _DebuggerService {
+    private final Map templateDebugInfos = new HashMap();
+    private final HashSet suspendedEnvironments = new HashSet();
+    private final Map listeners = new HashMap();
+    private final ReferenceQueue refQueue = new ReferenceQueue();
+     
+
+    private final RmiDebuggerImpl debugger;
+    private DebuggerServer server;
+
+    RmiDebuggerService() {
+        try {
+            debugger = new RmiDebuggerImpl(this);
+            server = new DebuggerServer((Serializable) RemoteObject.toStub(debugger));
+            server.start();
+        } catch (RemoteException e) {
+            e.printStackTrace();
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+    
+    @Override
+    List getBreakpointsSpi(String templateName) {
+        synchronized (templateDebugInfos) {
+            TemplateDebugInfo tdi = findTemplateDebugInfo(templateName);
+            return tdi == null ? Collections.EMPTY_LIST : tdi.breakpoints;
+        }
+    }
+
+    List getBreakpointsSpi() {
+        List sumlist = new ArrayList();
+        synchronized (templateDebugInfos) {
+            for (Iterator iter = templateDebugInfos.values().iterator(); iter.hasNext(); ) {
+                sumlist.addAll(((TemplateDebugInfo) iter.next()).breakpoints);
+            }
+        }
+        Collections.sort(sumlist);
+        return sumlist;
+    }
+
+    // TODO See in SuppressFBWarnings
+    @Override
+    @SuppressFBWarnings(value={ "UW_UNCOND_WAIT", "WA_NOT_IN_LOOP" }, justification="Will have to be re-desigend; postponed.")
+    boolean suspendEnvironmentSpi(Environment env, String templateName, int line)
+    throws RemoteException {
+        RmiDebuggedEnvironmentImpl denv = 
+            (RmiDebuggedEnvironmentImpl)
+                RmiDebuggedEnvironmentImpl.getCachedWrapperFor(env);
+                
+        synchronized (suspendedEnvironments) {
+            suspendedEnvironments.add(denv);
+        }
+        try {
+            EnvironmentSuspendedEvent breakpointEvent = 
+                new EnvironmentSuspendedEvent(this, templateName, line, denv);
+    
+            synchronized (listeners) {
+                for (Iterator iter = listeners.values().iterator(); iter.hasNext(); ) {
+                    DebuggerListener listener = (DebuggerListener) iter.next();
+                    listener.environmentSuspended(breakpointEvent);
+                }
+            }
+            synchronized (denv) {
+                try {
+                    denv.wait();
+                } catch (InterruptedException e) {
+                    // Intentionally ignored
+                }
+            }
+            return denv.isStopped();
+        } finally {
+            synchronized (suspendedEnvironments) {
+                suspendedEnvironments.remove(denv);
+            }
+        }
+    }
+    
+    @Override
+    void registerTemplateSpi(Template template) {
+        String templateName = template.getName();
+        synchronized (templateDebugInfos) {
+            TemplateDebugInfo tdi = createTemplateDebugInfo(templateName);
+            tdi.templates.add(new TemplateReference(templateName, template, refQueue));
+            // Inject already defined breakpoints into the template
+            for (Iterator iter = tdi.breakpoints.iterator(); iter.hasNext(); ) {
+                Breakpoint breakpoint = (Breakpoint) iter.next();
+                _Debug.insertDebugBreak(template, breakpoint.getLine());
+            }
+        }
+    }
+    
+    Collection getSuspendedEnvironments() {
+        return (Collection) suspendedEnvironments.clone();
+    }
+
+    Object addDebuggerListener(DebuggerListener listener) {
+        Object id; 
+        synchronized (listeners) {
+            id = Long.valueOf(System.currentTimeMillis());
+            listeners.put(id, listener);
+        }
+        return id;
+    }
+    
+    void removeDebuggerListener(Object id) {
+        synchronized (listeners) {
+            listeners.remove(id);
+        }
+    }
+
+    void addBreakpoint(Breakpoint breakpoint) {
+        String templateName = breakpoint.getTemplateName();
+        synchronized (templateDebugInfos) {
+            TemplateDebugInfo tdi = createTemplateDebugInfo(templateName);
+            List breakpoints = tdi.breakpoints;
+            int pos = Collections.binarySearch(breakpoints, breakpoint);
+            if (pos < 0) {
+                // Add to the list of breakpoints
+                breakpoints.add(-pos - 1, breakpoint);
+                // Inject the breakpoint into all templates with this name
+                for (Iterator iter = tdi.templates.iterator(); iter.hasNext(); ) {
+                    TemplateReference ref = (TemplateReference) iter.next();
+                    Template t = ref.getTemplate();
+                    if (t == null) {
+                        iter.remove();
+                    } else {
+                        _Debug.insertDebugBreak(t, breakpoint.getLine());
+                    }
+                }
+            }
+        }
+    }
+
+    private TemplateDebugInfo findTemplateDebugInfo(String templateName) {
+        processRefQueue();
+        return (TemplateDebugInfo) templateDebugInfos.get(templateName); 
+    }
+    
+    private TemplateDebugInfo createTemplateDebugInfo(String templateName) {
+        TemplateDebugInfo tdi = findTemplateDebugInfo(templateName);
+        if (tdi == null) {
+            tdi = new TemplateDebugInfo();
+            templateDebugInfos.put(templateName, tdi);
+        }
+        return tdi;
+    }
+    
+    void removeBreakpoint(Breakpoint breakpoint) {
+        String templateName = breakpoint.getTemplateName();
+        synchronized (templateDebugInfos) {
+            TemplateDebugInfo tdi = findTemplateDebugInfo(templateName);
+            if (tdi != null) {
+                List breakpoints = tdi.breakpoints;
+                int pos = Collections.binarySearch(breakpoints, breakpoint);
+                if (pos >= 0) { 
+                    breakpoints.remove(pos);
+                    for (Iterator iter = tdi.templates.iterator(); iter.hasNext(); ) {
+                        TemplateReference ref = (TemplateReference) iter.next();
+                        Template t = ref.getTemplate();
+                        if (t == null) {
+                            iter.remove();
+                        } else {
+                            _Debug.removeDebugBreak(t, breakpoint.getLine());
+                        }
+                    }
+                }
+                if (tdi.isEmpty()) {
+                    templateDebugInfos.remove(templateName);
+                }
+            }
+        }
+    }
+
+    void removeBreakpoints(String templateName) {
+        synchronized (templateDebugInfos) {
+            TemplateDebugInfo tdi = findTemplateDebugInfo(templateName);
+            if (tdi != null) {
+                removeBreakpoints(tdi);
+                if (tdi.isEmpty()) {
+                    templateDebugInfos.remove(templateName);
+                }
+            }
+        }
+    }
+
+    void removeBreakpoints() {
+        synchronized (templateDebugInfos) {
+            for (Iterator iter = templateDebugInfos.values().iterator(); iter.hasNext(); ) {
+                TemplateDebugInfo tdi = (TemplateDebugInfo) iter.next(); 
+                removeBreakpoints(tdi);
+                if (tdi.isEmpty()) {
+                    iter.remove();
+                }
+            }
+        }
+    }
+
+    private void removeBreakpoints(TemplateDebugInfo tdi) {
+        tdi.breakpoints.clear();
+        for (Iterator iter = tdi.templates.iterator(); iter.hasNext(); ) {
+            TemplateReference ref = (TemplateReference) iter.next();
+            Template t = ref.getTemplate();
+            if (t == null) {
+                iter.remove();
+            } else {
+                _Debug.removeDebugBreaks(t);
+            }
+        }
+    }
+
+    private static final class TemplateDebugInfo {
+        final List templates = new ArrayList();
+        final List breakpoints = new ArrayList();
+        
+        boolean isEmpty() {
+            return templates.isEmpty() && breakpoints.isEmpty();
+        }
+    }
+    
+    private static final class TemplateReference extends WeakReference {
+        final String templateName;
+         
+        TemplateReference(String templateName, Template template, ReferenceQueue queue) {
+            super(template, queue);
+            this.templateName = templateName;
+        }
+        
+        Template getTemplate() {
+            return (Template) get();
+        }
+    }
+    
+    private void processRefQueue() {
+        for (; ; ) {
+            TemplateReference ref = (TemplateReference) refQueue.poll();
+            if (ref == null) {
+                break;
+            }
+            TemplateDebugInfo tdi = findTemplateDebugInfo(ref.templateName);
+            if (tdi != null) {
+                tdi.templates.remove(ref);
+                if (tdi.isEmpty()) {
+                    templateDebugInfos.remove(ref.templateName);
+                }
+            }
+        }
+    }
+
+    @Override
+    void shutdownSpi() {
+        server.stop();
+        try {
+            UnicastRemoteObject.unexportObject(debugger, true);
+        } catch (Exception e) {
+        }
+
+        RmiDebuggedEnvironmentImpl.cleanup();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6939fd07/src/main/java/org/apache/freemarker/core/debug/SoftCache.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/debug/SoftCache.java b/src/main/java/org/apache/freemarker/core/debug/SoftCache.java
new file mode 100644
index 0000000..730574a
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/debug/SoftCache.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.freemarker.core.debug;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.util.Map;
+
+class SoftCache {
+    
+    private final ReferenceQueue queue = new ReferenceQueue();
+    private final Map map;
+    
+    public SoftCache(Map backingMap) {
+        map = backingMap;
+    }
+    
+    public Object get(Object key) {
+        processQueue();
+        Reference ref = (Reference) map.get(key);
+        return ref == null ? null : ref.get();
+    }
+
+    public void put(Object key, Object value) {
+        processQueue();
+        map.put(key, new SoftValueReference(key, value, queue));
+    }
+
+    public void remove(Object key) {
+        processQueue();
+        map.remove(key);
+    }
+
+    public void clear() {
+        map.clear();
+        processQueue();
+    }
+    
+    /**
+     * Returns a close approximation of the number of cache entries.
+     */
+    public int getSize() {
+        processQueue();
+        return map.size();
+    }
+
+    private void processQueue() {
+        for (; ; ) {
+            SoftValueReference ref = (SoftValueReference) queue.poll();
+            if (ref == null) {
+                return;
+            }
+            Object key = ref.getKey();
+            map.remove(key);
+        }
+    }
+
+    private static final class SoftValueReference extends SoftReference {
+        private final Object key;
+
+        SoftValueReference(Object key, Object value, ReferenceQueue queue) {
+            super(value, queue);
+            this.key = key;
+        }
+
+        Object getKey() {
+            return key;
+        }
+    }
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6939fd07/src/main/java/org/apache/freemarker/core/debug/_DebuggerService.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/debug/_DebuggerService.java b/src/main/java/org/apache/freemarker/core/debug/_DebuggerService.java
new file mode 100644
index 0000000..37d094c
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/debug/_DebuggerService.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.freemarker.core.debug;
+
+import java.rmi.RemoteException;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.Template;
+import org.apache.freemarker.core.util._SecurityUtil;
+
+/**
+ * Don't use this; used internally by FreeMarker, might changes without notice.
+ * This class provides debugging hooks for the core FreeMarker engine. It is
+ * not usable for anyone outside the FreeMarker core classes.
+ */
+public abstract class _DebuggerService {
+    private static final _DebuggerService INSTANCE = createInstance();
+    
+    private static _DebuggerService createInstance() {
+        // Creates the appropriate service class. If the debugging is turned
+        // off, this is a fast no-op service, otherwise it's the real-thing
+        // RMI service.
+        return 
+            _SecurityUtil.getSystemProperty("org.apache.freemarker.core.debug.password", null) == null
+            ? new NoOpDebuggerService()
+            : new RmiDebuggerService();
+    }
+
+    public static List getBreakpoints(String templateName) {
+        return INSTANCE.getBreakpointsSpi(templateName);
+    }
+    
+    abstract List getBreakpointsSpi(String templateName);
+
+    public static void registerTemplate(Template template) {
+        INSTANCE.registerTemplateSpi(template);
+    }
+    
+    abstract void registerTemplateSpi(Template template);
+    
+    public static boolean suspendEnvironment(Environment env, String templateName, int line)
+    throws RemoteException {
+        return INSTANCE.suspendEnvironmentSpi(env, templateName, line);
+    }
+    
+    abstract boolean suspendEnvironmentSpi(Environment env, String templateName, int line)
+    throws RemoteException;
+
+    abstract void shutdownSpi();
+
+    public static void shutdown() {
+        INSTANCE.shutdownSpi();
+    }
+
+    private static class NoOpDebuggerService extends _DebuggerService {
+        @Override
+        List getBreakpointsSpi(String templateName) {
+            return Collections.EMPTY_LIST;
+        }
+        
+        @Override
+        boolean suspendEnvironmentSpi(Environment env, String templateName, int line) {
+            throw new UnsupportedOperationException();
+        }
+        
+        @Override
+        void registerTemplateSpi(Template template) {
+        }
+
+        @Override
+        void shutdownSpi() {
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6939fd07/src/main/java/org/apache/freemarker/core/debug/impl/DebuggerServer.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/debug/impl/DebuggerServer.java b/src/main/java/org/apache/freemarker/core/debug/impl/DebuggerServer.java
deleted file mode 100644
index d664695..0000000
--- a/src/main/java/org/apache/freemarker/core/debug/impl/DebuggerServer.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.
- */
-
-package org.apache.freemarker.core.debug.impl;
-
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
-import java.io.UnsupportedEncodingException;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.security.MessageDigest;
-import java.security.SecureRandom;
-import java.util.Arrays;
-import java.util.Random;
-
-import org.apache.freemarker.core._CoreLogs;
-import org.apache.freemarker.core.debug.Debugger;
-import org.apache.freemarker.core.util.UndeclaredThrowableException;
-import org.apache.freemarker.core.util._SecurityUtil;
-import org.slf4j.Logger;
-
-/**
- */
-class DebuggerServer {
-    
-    private static final Logger LOG = _CoreLogs.DEBUG_SERVER;
-    
-    // TODO: Eventually replace with Yarrow    
-    // TODO: Can be extremely slow (on Linux, not enough entropy)
-    private static final Random R = new SecureRandom();
-    
-    private final byte[] password;
-    private final int port;
-    private final Serializable debuggerStub;
-    private boolean stop = false;
-    private ServerSocket serverSocket;
-    
-    public DebuggerServer(Serializable debuggerStub) {
-        port = _SecurityUtil.getSystemProperty("org.apache.freemarker.core.debug.port", Debugger.DEFAULT_PORT).intValue();
-        try {
-            password = _SecurityUtil.getSystemProperty("org.apache.freemarker.core.debug.password", "").getBytes("UTF-8");
-        } catch (UnsupportedEncodingException e) {
-            throw new UndeclaredThrowableException(e);
-        }
-        this.debuggerStub = debuggerStub;
-    }
-    
-    public void start() {
-        new Thread(new Runnable()
-        {
-            @Override
-            public void run() {
-                startInternal();
-            }
-        }, "FreeMarker Debugger Server Acceptor").start();
-    }
-    
-    private void startInternal() {
-        try {
-            serverSocket = new ServerSocket(port);
-            while (!stop) {
-                Socket s = serverSocket.accept();
-                new Thread(new DebuggerAuthProtocol(s)).start();
-            }
-        } catch (IOException e) {
-            LOG.error("Debugger server shut down.", e);
-        }
-    }
-    
-    private class DebuggerAuthProtocol implements Runnable {
-        private final Socket s;
-        
-        DebuggerAuthProtocol(Socket s) {
-            this.s = s;
-        }
-        
-        @Override
-        public void run() {
-            try {
-                ObjectOutputStream out = new ObjectOutputStream(s.getOutputStream());
-                ObjectInputStream in = new ObjectInputStream(s.getInputStream());
-                byte[] challenge = new byte[512];
-                R.nextBytes(challenge);
-                out.writeInt(220); // protocol version
-                out.writeObject(challenge);
-                MessageDigest md = MessageDigest.getInstance("SHA");
-                md.update(password);
-                md.update(challenge);
-                byte[] response = (byte[]) in.readObject();
-                if (Arrays.equals(response, md.digest())) {
-                    out.writeObject(debuggerStub);
-                } else {
-                    out.writeObject(null);
-                }
-            } catch (Exception e) {
-                LOG.warn("Connection to {} abruply broke", s.getInetAddress().getHostAddress(), e);
-            }
-        }
-
-    }
-
-    public void stop() {
-        stop = true;
-        if (serverSocket != null) {
-            try {
-                serverSocket.close();
-            } catch (IOException e) {
-                LOG.error("Unable to close server socket.", e);
-            }
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6939fd07/src/main/java/org/apache/freemarker/core/debug/impl/DebuggerService.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/debug/impl/DebuggerService.java b/src/main/java/org/apache/freemarker/core/debug/impl/DebuggerService.java
deleted file mode 100644
index 65ccb27..0000000
--- a/src/main/java/org/apache/freemarker/core/debug/impl/DebuggerService.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.
- */
-
-package org.apache.freemarker.core.debug.impl;
-
-import java.rmi.RemoteException;
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.freemarker.core.Environment;
-import org.apache.freemarker.core.Template;
-import org.apache.freemarker.core.util._SecurityUtil;
-
-/**
- * This class provides debugging hooks for the core FreeMarker engine. It is
- * not usable for anyone outside the FreeMarker core classes. It is public only
- * as an implementation detail.
- */
-public abstract class DebuggerService {
-    private static final DebuggerService instance = createInstance();
-    
-    private static DebuggerService createInstance() {
-        // Creates the appropriate service class. If the debugging is turned
-        // off, this is a fast no-op service, otherwise it's the real-thing
-        // RMI service.
-        return 
-            _SecurityUtil.getSystemProperty("org.apache.freemarker.core.debug.password", null) == null
-            ? new NoOpDebuggerService()
-            : new RmiDebuggerService();
-    }
-
-    public static List getBreakpoints(String templateName) {
-        return instance.getBreakpointsSpi(templateName);
-    }
-    
-    abstract List getBreakpointsSpi(String templateName);
-
-    public static void registerTemplate(Template template) {
-        instance.registerTemplateSpi(template);
-    }
-    
-    abstract void registerTemplateSpi(Template template);
-    
-    public static boolean suspendEnvironment(Environment env, String templateName, int line)
-    throws RemoteException {
-        return instance.suspendEnvironmentSpi(env, templateName, line);
-    }
-    
-    abstract boolean suspendEnvironmentSpi(Environment env, String templateName, int line)
-    throws RemoteException;
-
-    abstract void shutdownSpi();
-
-    public static void shutdown() {
-        instance.shutdownSpi();
-    }
-
-    private static class NoOpDebuggerService extends DebuggerService {
-        @Override
-        List getBreakpointsSpi(String templateName) {
-            return Collections.EMPTY_LIST;
-        }
-        
-        @Override
-        boolean suspendEnvironmentSpi(Environment env, String templateName, int line) {
-            throw new UnsupportedOperationException();
-        }
-        
-        @Override
-        void registerTemplateSpi(Template template) {
-        }
-
-        @Override
-        void shutdownSpi() {
-        }
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6939fd07/src/main/java/org/apache/freemarker/core/debug/impl/RmiDebugModelImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/debug/impl/RmiDebugModelImpl.java b/src/main/java/org/apache/freemarker/core/debug/impl/RmiDebugModelImpl.java
deleted file mode 100644
index 97da734..0000000
--- a/src/main/java/org/apache/freemarker/core/debug/impl/RmiDebugModelImpl.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.
- */
-
-package org.apache.freemarker.core.debug.impl;
-
-import java.rmi.RemoteException;
-import java.rmi.server.UnicastRemoteObject;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-import org.apache.freemarker.core.debug.DebugModel;
-import org.apache.freemarker.core.model.TemplateBooleanModel;
-import org.apache.freemarker.core.model.TemplateCollectionModel;
-import org.apache.freemarker.core.model.TemplateDateModel;
-import org.apache.freemarker.core.model.TemplateHashModel;
-import org.apache.freemarker.core.model.TemplateHashModelEx;
-import org.apache.freemarker.core.model.TemplateMethodModel;
-import org.apache.freemarker.core.model.TemplateMethodModelEx;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateModelIterator;
-import org.apache.freemarker.core.model.TemplateNumberModel;
-import org.apache.freemarker.core.model.TemplateScalarModel;
-import org.apache.freemarker.core.model.TemplateSequenceModel;
-import org.apache.freemarker.core.model.TemplateTransformModel;
-
-/**
- */
-class RmiDebugModelImpl extends UnicastRemoteObject implements DebugModel {
-    private static final long serialVersionUID = 1L;
-
-    private final TemplateModel model;
-    private final int type;
-    
-    RmiDebugModelImpl(TemplateModel model, int extraTypes) throws RemoteException {
-        super();
-        this.model = model;
-        type = calculateType(model) + extraTypes;
-    }
-
-    private static DebugModel getDebugModel(TemplateModel tm) throws RemoteException {
-        return (DebugModel) RmiDebuggedEnvironmentImpl.getCachedWrapperFor(tm);
-    }
-    @Override
-    public String getAsString() throws TemplateModelException {
-        return ((TemplateScalarModel) model).getAsString();
-    }
-
-    @Override
-    public Number getAsNumber() throws TemplateModelException {
-        return ((TemplateNumberModel) model).getAsNumber();
-    }
-
-    @Override
-    public Date getAsDate() throws TemplateModelException {
-        return ((TemplateDateModel) model).getAsDate();
-    }
-
-    @Override
-    public int getDateType() {
-        return ((TemplateDateModel) model).getDateType();
-    }
-
-    @Override
-    public boolean getAsBoolean() throws TemplateModelException {
-        return ((TemplateBooleanModel) model).getAsBoolean();
-    }
-
-    @Override
-    public int size() throws TemplateModelException {
-        if (model instanceof TemplateSequenceModel) {
-            return ((TemplateSequenceModel) model).size();
-        }
-        return ((TemplateHashModelEx) model).size();
-    }
-
-    @Override
-    public DebugModel get(int index) throws TemplateModelException, RemoteException {
-        return getDebugModel(((TemplateSequenceModel) model).get(index));
-    }
-    
-    @Override
-    public DebugModel[] get(int fromIndex, int toIndex) throws TemplateModelException, RemoteException {
-        DebugModel[] dm = new DebugModel[toIndex - fromIndex];
-        TemplateSequenceModel s = (TemplateSequenceModel) model;
-        for (int i = fromIndex; i < toIndex; i++) {
-            dm[i - fromIndex] = getDebugModel(s.get(i));
-        }
-        return dm;
-    }
-
-    @Override
-    public DebugModel[] getCollection() throws TemplateModelException, RemoteException {
-        List list = new ArrayList();
-        TemplateModelIterator i = ((TemplateCollectionModel) model).iterator();
-        while (i.hasNext()) {
-            list.add(getDebugModel(i.next()));
-        }
-        return (DebugModel[]) list.toArray(new DebugModel[list.size()]);
-    }
-    
-    @Override
-    public DebugModel get(String key) throws TemplateModelException, RemoteException {
-        return getDebugModel(((TemplateHashModel) model).get(key));
-    }
-    
-    @Override
-    public DebugModel[] get(String[] keys) throws TemplateModelException, RemoteException {
-        DebugModel[] dm = new DebugModel[keys.length];
-        TemplateHashModel h = (TemplateHashModel) model;
-        for (int i = 0; i < keys.length; i++) {
-            dm[i] = getDebugModel(h.get(keys[i]));
-        }
-        return dm;
-    }
-
-    @Override
-    public String[] keys() throws TemplateModelException {
-        TemplateHashModelEx h = (TemplateHashModelEx) model;
-        List list = new ArrayList();
-        TemplateModelIterator i = h.keys().iterator();
-        while (i.hasNext()) {
-            list.add(((TemplateScalarModel) i.next()).getAsString());
-        }
-        return (String[]) list.toArray(new String[list.size()]);
-    }
-
-    @Override
-    public int getModelTypes() {
-        return type;
-    }
-    
-    private static int calculateType(TemplateModel model) {
-        int type = 0;
-        if (model instanceof TemplateScalarModel) type += TYPE_SCALAR;
-        if (model instanceof TemplateNumberModel) type += TYPE_NUMBER;
-        if (model instanceof TemplateDateModel) type += TYPE_DATE;
-        if (model instanceof TemplateBooleanModel) type += TYPE_BOOLEAN;
-        if (model instanceof TemplateSequenceModel) type += TYPE_SEQUENCE;
-        if (model instanceof TemplateCollectionModel) type += TYPE_COLLECTION;
-        if (model instanceof TemplateHashModelEx) type += TYPE_HASH_EX;
-        else if (model instanceof TemplateHashModel) type += TYPE_HASH;
-        if (model instanceof TemplateMethodModelEx) type += TYPE_METHOD_EX;
-        else if (model instanceof TemplateMethodModel) type += TYPE_METHOD;
-        if (model instanceof TemplateTransformModel) type += TYPE_TRANSFORM;
-        return type;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6939fd07/src/main/java/org/apache/freemarker/core/debug/impl/RmiDebuggedEnvironmentImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/debug/impl/RmiDebuggedEnvironmentImpl.java b/src/main/java/org/apache/freemarker/core/debug/impl/RmiDebuggedEnvironmentImpl.java
deleted file mode 100644
index eb89830..0000000
--- a/src/main/java/org/apache/freemarker/core/debug/impl/RmiDebuggedEnvironmentImpl.java
+++ /dev/null
@@ -1,337 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.
- */
-
-package org.apache.freemarker.core.debug.impl;
-
-import java.rmi.Remote;
-import java.rmi.RemoteException;
-import java.rmi.server.UnicastRemoteObject;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-import org.apache.freemarker.core.Configurable;
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.Environment;
-import org.apache.freemarker.core.Template;
-import org.apache.freemarker.core.debug.DebugModel;
-import org.apache.freemarker.core.debug.DebuggedEnvironment;
-import org.apache.freemarker.core.model.TemplateCollectionModel;
-import org.apache.freemarker.core.model.TemplateHashModelEx;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.impl.SimpleCollection;
-import org.apache.freemarker.core.model.impl.SimpleScalar;
-import org.apache.freemarker.core.util.UndeclaredThrowableException;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-@SuppressWarnings("serial")
-class RmiDebuggedEnvironmentImpl extends RmiDebugModelImpl implements DebuggedEnvironment {
-
-    private static final SoftCache CACHE = new SoftCache(new IdentityHashMap());
-    private static final Object ID_LOCK = new Object();
-    
-    private static long nextId = 1;
-    private static Set remotes = new HashSet();
-
-    
-    private boolean stopped = false;
-    private final long id;
-    
-    private RmiDebuggedEnvironmentImpl(Environment env) throws RemoteException {
-        super(new DebugEnvironmentModel(env), DebugModel.TYPE_ENVIRONMENT);
-        synchronized (ID_LOCK) {
-            id = nextId++;
-        }
-    }
-
-    static synchronized Object getCachedWrapperFor(Object key)
-    throws RemoteException {
-        Object value = CACHE.get(key);
-        if (value == null) {
-            if (key instanceof TemplateModel) {
-                int extraTypes;
-                if (key instanceof DebugConfigurationModel) {
-                    extraTypes = DebugModel.TYPE_CONFIGURATION;
-                } else if (key instanceof DebugTemplateModel) {
-                    extraTypes = DebugModel.TYPE_TEMPLATE;
-                } else {
-                    extraTypes = 0;
-                }
-                value = new RmiDebugModelImpl((TemplateModel) key, extraTypes);
-            } else if (key instanceof Environment) {
-                value = new RmiDebuggedEnvironmentImpl((Environment) key); 
-            } else if (key instanceof Template) {
-                value = new DebugTemplateModel((Template) key);
-            } else if (key instanceof Configuration) {
-                value = new DebugConfigurationModel((Configuration) key);
-            }
-        }
-        if (value != null) {
-            CACHE.put(key, value);
-        }
-        if (value instanceof Remote) {
-            remotes.add(value);
-        }
-        return value;
-    }
-
-    // TODO See in SuppressFBWarnings
-    @Override
-    @SuppressFBWarnings(value="NN_NAKED_NOTIFY", justification="Will have to be re-desigend; postponed.")
-    public void resume() {
-        synchronized (this) {
-            notify();
-        }
-    }
-
-    @Override
-    public void stop() {
-        stopped = true;
-        resume();
-    }
-
-    @Override
-    public long getId() {
-        return id;
-    }
-    
-    boolean isStopped() {
-        return stopped;
-    }
-    
-    private abstract static class DebugMapModel implements TemplateHashModelEx {
-        @Override
-        public int size() {
-            return keySet().size();
-        }
-
-        @Override
-        public TemplateCollectionModel keys() {
-            return new SimpleCollection(keySet());
-        }
-
-        @Override
-        public TemplateCollectionModel values() throws TemplateModelException {
-            Collection keys = keySet();
-            List list = new ArrayList(keys.size());
-            
-            for (Iterator it = keys.iterator(); it.hasNext(); ) {
-                list.add(get((String) it.next()));
-            }
-            return new SimpleCollection(list);
-        }
-
-        @Override
-        public boolean isEmpty() {
-            return size() == 0;
-        }
-        
-        abstract Collection keySet();
-
-        static List composeList(Collection c1, Collection c2) {
-            List list = new ArrayList(c1);
-            list.addAll(c2);
-            Collections.sort(list);
-            return list;
-        }
-    }
-    
-    private static class DebugConfigurableModel extends DebugMapModel {
-        static final List KEYS = Arrays.asList(
-                Configurable.ARITHMETIC_ENGINE_KEY,
-                Configurable.BOOLEAN_FORMAT_KEY,
-                Configurable.LOCALE_KEY,
-                Configurable.NUMBER_FORMAT_KEY,
-                Configurable.OBJECT_WRAPPER_KEY,
-                Configurable.TEMPLATE_EXCEPTION_HANDLER_KEY);
-
-        final Configurable configurable;
-        
-        DebugConfigurableModel(Configurable configurable) {
-            this.configurable = configurable;
-        }
-        
-        @Override
-        Collection keySet() {
-            return KEYS;
-        }
-        
-        @Override
-        public TemplateModel get(String key) throws TemplateModelException {
-            return null; // TODO
-        }
-
-    }
-    
-    private static class DebugConfigurationModel extends DebugConfigurableModel {
-        private static final List KEYS = composeList(DebugConfigurableModel.KEYS, Collections.singleton("sharedVariables"));
-
-        private TemplateModel sharedVariables = new DebugMapModel()
-        {
-            @Override
-            Collection keySet() {
-                return ((Configuration) configurable).getSharedVariableNames();
-            }
-        
-            @Override
-            public TemplateModel get(String key) {
-                return ((Configuration) configurable).getSharedVariable(key);
-            }
-        };
-        
-        DebugConfigurationModel(Configuration config) {
-            super(config);
-        }
-        
-        @Override
-        Collection keySet() {
-            return KEYS;
-        }
-
-        @Override
-        public TemplateModel get(String key) throws TemplateModelException {
-            if ("sharedVariables".equals(key)) {
-                return sharedVariables; 
-            } else {
-                return super.get(key);
-            }
-        }
-    }
-    
-    private static class DebugTemplateModel extends DebugConfigurableModel {
-        private static final List KEYS = composeList(DebugConfigurableModel.KEYS, 
-            Arrays.asList("configuration", "name"));
-    
-        private final SimpleScalar name;
-
-        DebugTemplateModel(Template template) {
-            super(template);
-            name = new SimpleScalar(template.getName());
-        }
-
-        @Override
-        Collection keySet() {
-            return KEYS;
-        }
-
-        @Override
-        public TemplateModel get(String key) throws TemplateModelException {
-            if ("configuration".equals(key)) {
-                try {
-                    return (TemplateModel) getCachedWrapperFor(((Template) configurable).getConfiguration());
-                } catch (RemoteException e) {
-                    throw new TemplateModelException(e);
-                }
-            }
-            if ("name".equals(key)) {
-                return name;
-            }
-            return super.get(key);
-        }
-    }
-
-    private static class DebugEnvironmentModel extends DebugConfigurableModel {
-        private static final List KEYS = composeList(DebugConfigurableModel.KEYS, 
-            Arrays.asList(
-                    "currentNamespace",
-                    "dataModel",
-                    "globalNamespace",
-                    "knownVariables",
-                    "mainNamespace",
-                    "template"));
-    
-        private TemplateModel knownVariables = new DebugMapModel()
-        {
-            @Override
-            Collection keySet() {
-                try {
-                    return ((Environment) configurable).getKnownVariableNames();
-                } catch (TemplateModelException e) {
-                    throw new UndeclaredThrowableException(e);
-                }
-            }
-        
-            @Override
-            public TemplateModel get(String key) throws TemplateModelException {
-                return ((Environment) configurable).getVariable(key);
-            }
-        };
-         
-        DebugEnvironmentModel(Environment env) {
-            super(env);
-        }
-
-        @Override
-        Collection keySet() {
-            return KEYS;
-        }
-
-        @Override
-        public TemplateModel get(String key) throws TemplateModelException {
-            if ("currentNamespace".equals(key)) {
-                return ((Environment) configurable).getCurrentNamespace();
-            }
-            if ("dataModel".equals(key)) {
-                return ((Environment) configurable).getDataModel();
-            }
-            if ("globalNamespace".equals(key)) {
-                return ((Environment) configurable).getGlobalNamespace();
-            }
-            if ("knownVariables".equals(key)) {
-                return knownVariables;
-            }
-            if ("mainNamespace".equals(key)) {
-                return ((Environment) configurable).getMainNamespace();
-            }
-            if ("mainTemplate".equals(key)) {
-                try {
-                    return (TemplateModel) getCachedWrapperFor(((Environment) configurable).getMainTemplate());
-                } catch (RemoteException e) {
-                    throw new TemplateModelException(e);
-                }
-            }
-            if ("currentTemplate".equals(key)) {
-                try {
-                    return (TemplateModel) getCachedWrapperFor(((Environment) configurable).getCurrentTemplate());
-                } catch (RemoteException e) {
-                    throw new TemplateModelException(e);
-                }
-            }
-            return super.get(key);
-        }
-    }
-
-    public static void cleanup() {
-        for (Iterator i = remotes.iterator(); i.hasNext(); ) {
-            Object remoteObject = i.next();
-            try {
-                UnicastRemoteObject.unexportObject((Remote) remoteObject, true);
-            } catch (Exception e) {
-            }
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6939fd07/src/main/java/org/apache/freemarker/core/debug/impl/RmiDebuggerImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/debug/impl/RmiDebuggerImpl.java b/src/main/java/org/apache/freemarker/core/debug/impl/RmiDebuggerImpl.java
deleted file mode 100644
index 9fcb3a8..0000000
--- a/src/main/java/org/apache/freemarker/core/debug/impl/RmiDebuggerImpl.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.
- */
-
-package org.apache.freemarker.core.debug.impl;
-
-import java.rmi.RemoteException;
-import java.rmi.server.UnicastRemoteObject;
-import java.util.Collection;
-import java.util.List;
-
-import org.apache.freemarker.core.debug.Breakpoint;
-import org.apache.freemarker.core.debug.Debugger;
-import org.apache.freemarker.core.debug.DebuggerListener;
-
-/**
- */
-class RmiDebuggerImpl
-extends
-    UnicastRemoteObject
-implements
-    Debugger {
-    private static final long serialVersionUID = 1L;
-
-    private final RmiDebuggerService service;
-    
-    protected RmiDebuggerImpl(RmiDebuggerService service) throws RemoteException {
-        this.service = service;
-    }
-
-    @Override
-    public void addBreakpoint(Breakpoint breakpoint) {
-        service.addBreakpoint(breakpoint);
-    }
-
-    @Override
-    public Object addDebuggerListener(DebuggerListener listener) {
-        return service.addDebuggerListener(listener);
-    }
-
-    @Override
-    public List getBreakpoints() {
-        return service.getBreakpointsSpi();
-    }
-
-    @Override
-    public List getBreakpoints(String templateName) {
-        return service.getBreakpointsSpi(templateName);
-    }
-
-    @Override
-    public Collection getSuspendedEnvironments() {
-        return service.getSuspendedEnvironments();
-    }
-
-    @Override
-    public void removeBreakpoint(Breakpoint breakpoint) {
-        service.removeBreakpoint(breakpoint);
-    }
-
-    @Override
-    public void removeDebuggerListener(Object id) {
-        service.removeDebuggerListener(id);
-    }
-
-    @Override
-    public void removeBreakpoints() {
-        service.removeBreakpoints();
-    }
-
-    @Override
-    public void removeBreakpoints(String templateName) {
-        service.removeBreakpoints(templateName);
-    }
-}


Mime
View raw message