freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [27/51] [partial] incubator-freemarker git commit: Migrated from Ant to Gradle, and modularized the project. This is an incomplete migration; there are some TODO-s in the build scripts, and release related tasks are still missing. What works: Building th
Date Sun, 14 May 2017 10:53:10 GMT
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebugModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebugModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebugModel.java
new file mode 100644
index 0000000..b5e0a58
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebugModel.java
@@ -0,0 +1,105 @@
+/*
+ * 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.util.Date;
+
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+ * Represents the debugger-side mirror of a TemplateModel object, a Template
+ * object, or a Configuration object. The Environment objects are also represented
+ * by instances of this model, although not directly but through a separate
+ * subinterface {@link DebuggedEnvironment}. The interface is a union of
+ * almost all of FreeMarker template models with identical method signatures.
+ * For purposes of optimizing network traffic there are bulk retrieval methods
+ * for sequences and hashes, as well as a {@link #getModelTypes()} method that
+ * returns a bit mask of various <tt>TYPE_xxx</tt> constants flagging which
+ * template models are implemented by the mirrored object.
+ */
+public interface DebugModel extends Remote {
+    public static final int TYPE_SCALAR        =    1;
+    public static final int TYPE_NUMBER        =    2;
+    public static final int TYPE_DATE          =    4;
+    public static final int TYPE_BOOLEAN       =    8;
+    public static final int TYPE_SEQUENCE      =   16;
+    public static final int TYPE_COLLECTION    =   32;
+    public static final int TYPE_HASH          =   64;
+    public static final int TYPE_HASH_EX       =  128;
+    public static final int TYPE_METHOD        =  256;
+    public static final int TYPE_METHOD_EX     =  512;
+    public static final int TYPE_TRANSFORM     = 1024;
+    public static final int TYPE_ENVIRONMENT   = 2048;
+    public static final int TYPE_TEMPLATE      = 4096;
+    public static final int TYPE_CONFIGURATION = 8192;
+    
+    public String getAsString()
+    throws TemplateModelException,
+        RemoteException;
+        
+    public Number getAsNumber()
+    throws TemplateModelException,
+        RemoteException;
+    
+    public boolean getAsBoolean()
+    throws TemplateModelException,
+        RemoteException;
+    
+    public Date getAsDate()
+    throws TemplateModelException,
+        RemoteException;
+    
+    public int getDateType()
+    throws TemplateModelException,
+        RemoteException;
+        
+    public int size()
+    throws TemplateModelException,
+        RemoteException;
+        
+    public DebugModel get(int index)
+    throws TemplateModelException,
+        RemoteException;
+    
+    public DebugModel[] get(int fromIndex, int toIndex)
+    throws TemplateModelException,
+        RemoteException;
+        
+    public DebugModel get(String key)
+    throws TemplateModelException,
+        RemoteException;
+        
+    public DebugModel[] get(String[] keys)
+    throws TemplateModelException,
+        RemoteException;
+    
+    public DebugModel[] getCollection()
+    throws TemplateModelException,
+        RemoteException;
+
+    public String[] keys()
+    throws TemplateModelException,
+        RemoteException;
+    
+    public int getModelTypes()
+    throws RemoteException;
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggedEnvironment.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggedEnvironment.java b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggedEnvironment.java
new file mode 100644
index 0000000..dca312d
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggedEnvironment.java
@@ -0,0 +1,58 @@
+/*
+ * 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 org.apache.freemarker.core.MutableProcessingConfiguration;
+
+/**
+ * Represents the debugger-side mirror of a debugged 
+ * {@link org.apache.freemarker.core.Environment} object in the remote VM. This interface
+ * extends {@link DebugModel}, and the properties of the Environment are exposed
+ * as hash keys on it. Specifically, the following keys are supported:
+ * "currentNamespace", "dataModel", "globalNamespace", "knownVariables", 
+ * "mainNamespace", and "template".
+ * <p>The debug model for the template supports keys "configuration" and "name".
+ * <p>The debug model for the configuration supports key "sharedVariables".
+ * <p>Additionally, all of the debug models for environment, template, and 
+ * configuration also support all the setting keys of 
+ * {@link MutableProcessingConfiguration} objects.
+
+ */
+public interface DebuggedEnvironment extends DebugModel {
+    /**
+     * Resumes the processing of the environment in the remote VM after it was 
+     * stopped on a breakpoint.
+     */
+    public void resume() throws RemoteException;
+    
+    /**
+     * Stops the processing of the environment after it was stopped on
+     * a breakpoint. Causes a {@link org.apache.freemarker.core.StopException} to be
+     * thrown in the processing thread in the remote VM. 
+     */
+    public void stop() throws RemoteException;
+    
+    /**
+     * Returns a unique identifier for this environment
+     */
+    public long getId() throws RemoteException;
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/debug/Debugger.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/Debugger.java b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/Debugger.java
new file mode 100644
index 0000000..3e2b8de
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/Debugger.java
@@ -0,0 +1,95 @@
+/*
+ * 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.util.Collection;
+import java.util.List;
+
+/**
+ * The main debugger interface. Allows management of breakpoints as well as
+ * installation of listeners for debug events.
+ */
+public interface Debugger extends Remote {
+    public static final int DEFAULT_PORT = 7011;
+
+    /**
+     * Adds a breakpoint
+     * @param breakpoint the breakpoint to add
+     */
+    public void addBreakpoint(Breakpoint breakpoint)
+    throws RemoteException;
+    
+    /**
+     * Removes a single breakpoint
+     * @param breakpoint the breakpoint to remove
+     */
+    public void removeBreakpoint(Breakpoint breakpoint)
+    throws RemoteException;
+
+    /**
+     * Removes all breakpoints for a specific template
+     */
+    public void removeBreakpoints(String templateName)
+    throws RemoteException;
+
+    /**
+     * Removes all breakpoints
+     */
+    public void removeBreakpoints()
+    throws RemoteException;
+
+    /**
+     * Retrieves a list of all {@link Breakpoint} objects.
+     */
+    public List getBreakpoints()
+    throws RemoteException;
+        
+    /**
+     * Retrieves a list of all {@link Breakpoint} objects for the specified
+     * template.
+     */
+    public List getBreakpoints(String templateName)
+    throws RemoteException;
+
+    /**
+     * Retrieves a collection of all {@link DebuggedEnvironment} objects that 
+     * are currently suspended.
+     */
+    public Collection getSuspendedEnvironments()
+    throws RemoteException;
+        
+    /**
+     * Adds a listener for debugger events.
+     * @return an identification token that should be passed to 
+     * {@link #removeDebuggerListener(Object)} to remove this listener.
+     */
+    public Object addDebuggerListener(DebuggerListener listener)
+    throws RemoteException;
+        
+    /**
+     * Removes a previously added debugger listener.
+     * @param id the identification token for the listener that was returned
+     * from a prior call to {@link #addDebuggerListener(DebuggerListener)}.
+     */
+    public void removeDebuggerListener(Object id)
+    throws RemoteException;
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggerClient.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggerClient.java b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggerClient.java
new file mode 100644
index 0000000..2af3136
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggerClient.java
@@ -0,0 +1,149 @@
+/*
+ * 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.net.InetAddress;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.rmi.RemoteException;
+import java.rmi.server.RemoteObject;
+import java.security.MessageDigest;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.freemarker.core.util.UndeclaredThrowableException;
+
+/**
+ * A utility class that allows you to connect to the FreeMarker debugger service
+ * running on a specific host and port. 
+ */
+public class DebuggerClient {
+    private DebuggerClient() {
+    }
+    
+    /**
+     * Connects to the FreeMarker debugger service running on a specific host
+     * and port. The Java VM to which the connection is made must have defined
+     * the system property <tt>org.apache.freemarker.core.debug.password</tt> in order to enable
+     * the debugger service. Additionally, the <tt>org.apache.freemarker.core.debug.port</tt>
+     * system property can be set to specify the port where the debugger service
+     * is listening. When not specified, it defaults to 
+     * {@link Debugger#DEFAULT_PORT}.
+     * @param host the host address of the machine where the debugger service is
+     * running.
+     * @param port the port of the debugger service
+     * @param password the password required to connect to the debugger service
+     * @return Debugger a debugger object. null is returned in case incorrect
+     * password was supplied.
+     * @throws IOException if an exception occurs.
+     */
+    public static Debugger getDebugger(InetAddress host, int port, String password)
+    throws IOException {
+        try {
+            Socket s = new Socket(host, port);
+            try {
+                ObjectOutputStream out = new ObjectOutputStream(s.getOutputStream());
+                ObjectInputStream in = new ObjectInputStream(s.getInputStream());
+                int protocolVersion = in.readInt();
+                if (protocolVersion > 220) {
+                    throw new IOException(
+                        "Incompatible protocol version " + protocolVersion + 
+                        ". At most 220 was expected.");
+                }
+                byte[] challenge = (byte[]) in.readObject();
+                MessageDigest md = MessageDigest.getInstance("SHA");
+                md.update(password.getBytes(StandardCharsets.UTF_8));
+                md.update(challenge);
+                out.writeObject(md.digest());
+                return new LocalDebuggerProxy((Debugger) in.readObject());
+                //return (Debugger)in.readObject();
+            } finally {
+                s.close();
+            }
+        } catch (IOException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new UndeclaredThrowableException(e); 
+        }
+    }
+    
+    private static class LocalDebuggerProxy implements Debugger {
+        private final Debugger remoteDebugger;
+
+        LocalDebuggerProxy(Debugger remoteDebugger) {
+            this.remoteDebugger = remoteDebugger;
+        }
+
+        @Override
+        public void addBreakpoint(Breakpoint breakpoint) throws RemoteException {
+            remoteDebugger.addBreakpoint(breakpoint);
+        }
+
+        @Override
+        public Object addDebuggerListener(DebuggerListener listener)
+        throws RemoteException {
+            if (listener instanceof RemoteObject) {
+                return remoteDebugger.addDebuggerListener(listener);
+            } else {
+                RmiDebuggerListenerImpl remotableListener = 
+                    new RmiDebuggerListenerImpl(listener);
+                return remoteDebugger.addDebuggerListener(remotableListener);
+            }
+        }
+
+        @Override
+        public List getBreakpoints() throws RemoteException {
+            return remoteDebugger.getBreakpoints();
+        }
+
+        @Override
+        public List getBreakpoints(String templateName) throws RemoteException {
+            return remoteDebugger.getBreakpoints(templateName);
+        }
+
+        @Override
+        public Collection getSuspendedEnvironments() throws RemoteException {
+            return remoteDebugger.getSuspendedEnvironments();
+        }
+
+        @Override
+        public void removeBreakpoint(Breakpoint breakpoint) throws RemoteException {
+            remoteDebugger.removeBreakpoint(breakpoint);
+        }
+
+        @Override
+        public void removeBreakpoints(String templateName) throws RemoteException {
+            remoteDebugger.removeBreakpoints(templateName);
+        }
+
+        @Override
+        public void removeBreakpoints() throws RemoteException {
+            remoteDebugger.removeBreakpoints();
+        }
+
+        @Override
+        public void removeDebuggerListener(Object id) throws RemoteException {
+            remoteDebugger.removeDebuggerListener(id);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggerListener.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggerListener.java b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggerListener.java
new file mode 100644
index 0000000..a332426
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggerListener.java
@@ -0,0 +1,36 @@
+/*
+ * 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.util.EventListener;
+
+/**
+ * An interface for components that wish to receive debugging events.
+ */
+public interface DebuggerListener extends Remote, EventListener {
+    /**
+     * Called whenever an environment gets suspended (ie hits a breakpoint).
+     * @param e the event object
+     */
+    public void environmentSuspended(EnvironmentSuspendedEvent e)
+    throws RemoteException;
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggerServer.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggerServer.java b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggerServer.java
new file mode 100644
index 0000000..29fa199
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggerServer.java
@@ -0,0 +1,131 @@
+/*
+ * 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.net.ServerSocket;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
+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.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(
+                    StandardCharsets.UTF_8);
+        } catch (UnsupportedCharsetException 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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/debug/EnvironmentSuspendedEvent.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/EnvironmentSuspendedEvent.java b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/EnvironmentSuspendedEvent.java
new file mode 100644
index 0000000..5be10e6
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/EnvironmentSuspendedEvent.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.util.EventObject;
+
+/**
+ * Event describing a suspension of an environment (ie because it hit a
+ * breakpoint).
+ */
+public class EnvironmentSuspendedEvent extends EventObject {
+    private static final long serialVersionUID = 1L;
+
+    private final String name;
+    private final int line;
+    private final DebuggedEnvironment env;
+
+    public EnvironmentSuspendedEvent(Object source, String name, int line, DebuggedEnvironment env) {
+        super(source);
+        this.name = name;
+        this.line = line;
+        this.env = env;
+    }
+
+    /**
+     * The name of the template where the execution of the environment
+     * was suspended
+     * @return String the template name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * The line number in the template where the execution of the environment
+     * was suspended.
+     * @return int the line number
+     */
+    public int getLine() {
+        return line;
+    }
+
+    /**
+     * The environment that was suspended
+     * @return DebuggedEnvironment
+     */
+    public DebuggedEnvironment getEnvironment() {
+        return env;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebugModelImpl.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebugModelImpl.java b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebugModelImpl.java
new file mode 100644
index 0000000..bb11db3
--- /dev/null
+++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java
new file mode 100644
index 0000000..38b1d0a
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java
@@ -0,0 +1,340 @@
+/*
+ * 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.Configuration;
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.MutableProcessingConfiguration;
+import org.apache.freemarker.core.ProcessingConfiguration;
+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.DefaultObjectWrapper;
+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 static final DefaultObjectWrapper OBJECT_WRAPPER = new DefaultObjectWrapper.Builder(Configuration
+            .VERSION_3_0_0)
+            .build();
+    
+    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(), OBJECT_WRAPPER);
+        }
+
+        @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, OBJECT_WRAPPER);
+        }
+
+        @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(
+                MutableProcessingConfiguration.ARITHMETIC_ENGINE_KEY,
+                MutableProcessingConfiguration.BOOLEAN_FORMAT_KEY,
+                MutableProcessingConfiguration.LOCALE_KEY,
+                MutableProcessingConfiguration.NUMBER_FORMAT_KEY,
+                MutableProcessingConfiguration.OBJECT_WRAPPER_KEY,
+                MutableProcessingConfiguration.TEMPLATE_EXCEPTION_HANDLER_KEY);
+
+        final ProcessingConfiguration ProcessingConfiguration;
+        
+        DebugConfigurableModel(ProcessingConfiguration processingConfiguration) {
+            this.ProcessingConfiguration = processingConfiguration;
+        }
+        
+        @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) ProcessingConfiguration).getSharedVariables().keySet();
+            }
+        
+            @Override
+            public TemplateModel get(String key) {
+                return ((Configuration) ProcessingConfiguration).getWrappedSharedVariable(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.getLookupName());
+        }
+
+        @Override
+        Collection keySet() {
+            return KEYS;
+        }
+
+        @Override
+        public TemplateModel get(String key) throws TemplateModelException {
+            if ("configuration".equals(key)) {
+                try {
+                    return (TemplateModel) getCachedWrapperFor(((Template) ProcessingConfiguration).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) ProcessingConfiguration).getKnownVariableNames();
+                } catch (TemplateModelException e) {
+                    throw new UndeclaredThrowableException(e);
+                }
+            }
+        
+            @Override
+            public TemplateModel get(String key) throws TemplateModelException {
+                return ((Environment) ProcessingConfiguration).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) ProcessingConfiguration).getCurrentNamespace();
+            }
+            if ("dataModel".equals(key)) {
+                return ((Environment) ProcessingConfiguration).getDataModel();
+            }
+            if ("globalNamespace".equals(key)) {
+                return ((Environment) ProcessingConfiguration).getGlobalNamespace();
+            }
+            if ("knownVariables".equals(key)) {
+                return knownVariables;
+            }
+            if ("mainNamespace".equals(key)) {
+                return ((Environment) ProcessingConfiguration).getMainNamespace();
+            }
+            if ("mainTemplate".equals(key)) {
+                try {
+                    return (TemplateModel) getCachedWrapperFor(((Environment) ProcessingConfiguration).getMainTemplate());
+                } catch (RemoteException e) {
+                    throw new TemplateModelException(e);
+                }
+            }
+            if ("currentTemplate".equals(key)) {
+                try {
+                    return (TemplateModel) getCachedWrapperFor(((Environment) ProcessingConfiguration).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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerImpl.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerImpl.java b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerImpl.java
new file mode 100644
index 0000000..ea54e4e
--- /dev/null
+++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerListenerImpl.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerListenerImpl.java b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerListenerImpl.java
new file mode 100644
index 0000000..28985ec
--- /dev/null
+++ b/freemarker-core/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 invoke 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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerService.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerService.java b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerService.java
new file mode 100644
index 0000000..e44d398
--- /dev/null
+++ b/freemarker-core/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.getLookupName();
+        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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/debug/SoftCache.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/SoftCache.java b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/SoftCache.java
new file mode 100644
index 0000000..730574a
--- /dev/null
+++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/debug/_DebuggerService.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/_DebuggerService.java b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/_DebuggerService.java
new file mode 100644
index 0000000..37d094c
--- /dev/null
+++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/debug/package.html
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/package.html b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/package.html
new file mode 100644
index 0000000..677b842
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/package.html
@@ -0,0 +1,27 @@
+<!--
+  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.
+-->
+<html>
+<head>
+</head>
+<body bgcolor="white">
+<p>Debugging API; experimental status, might change!
+This is to support debugging in IDE-s. If you are working on a client for this,
+don't hesitate to contact us!</p>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/AdapterTemplateModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/AdapterTemplateModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/AdapterTemplateModel.java
new file mode 100644
index 0000000..9dc6587
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/AdapterTemplateModel.java
@@ -0,0 +1,49 @@
+/*
+ * 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.model;
+
+/**
+ * A {@link TemplateModel} that can be unwrapped and then it considers a provided desired (hint) class. This is
+ * useful when multiple languages has to communicate with each other through FreeMarker. For example, if we have a
+ * model that wraps a Jython object, then we have to unwrap that differently when we pass it to plain Java method and
+ * when we pass it to a Jython method.
+ * 
+ * <p>This is rarely implemented by applications. It is typically implemented by the model classes belonging to
+ * {@link ObjectWrapper}-s.
+ */
+public interface AdapterTemplateModel extends TemplateModel {
+    /**
+     * Retrieves the underlying object, or some other object semantically 
+     * equivalent to its value narrowed by the class hint.   
+     * @param hint the desired class of the returned value. An implementation 
+     * should make reasonable effort to retrieve an object of the requested 
+     * class, but if that is impossible, it must at least return the underlying 
+     * object as-is. As a minimal requirement, an implementation must always 
+     * return the exact underlying object when 
+     * <tt>hint.isInstance(underlyingObject)</tt> holds. When called 
+     * with <tt>java.lang.Object.class</tt>, it should return a generic Java 
+     * object (i.e. if the model is wrapping a scripting language object that is
+     * further wrapping a Java object, the deepest underlying Java object should
+     * be returned). 
+     * @return the underlying object, or its value accommodated for the hint
+     * class.
+     */
+    Object getAdaptedObject(Class<?> hint);
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/Constants.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/Constants.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/Constants.java
new file mode 100644
index 0000000..268188d
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/Constants.java
@@ -0,0 +1,133 @@
+/*
+ * 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.model;
+
+import java.io.Serializable;
+
+import org.apache.freemarker.core.model.impl.SimpleNumber;
+
+/**
+ * Frequently used constant {@link TemplateModel} values.
+ * 
+ * <p>These constants should be stored in the {@link TemplateModel}
+ * sub-interfaces, but for bacward compatibility they are stored here instead.
+ * Starting from FreeMarker 2.4 they should be copyed (not moved!) into the
+ * {@link TemplateModel} sub-interfaces, and this class should be marked as
+ * deprecated.</p>
+ */
+public class Constants {
+
+    public static final TemplateBooleanModel TRUE = TemplateBooleanModel.TRUE;
+
+    public static final TemplateBooleanModel FALSE = TemplateBooleanModel.FALSE;
+    
+    public static final TemplateScalarModel EMPTY_STRING = (TemplateScalarModel) TemplateScalarModel.EMPTY_STRING;
+
+    public static final TemplateNumberModel ZERO = new SimpleNumber(0);
+    
+    public static final TemplateNumberModel ONE = new SimpleNumber(1);
+    
+    public static final TemplateNumberModel MINUS_ONE = new SimpleNumber(-1);
+    
+    public static final TemplateModelIterator EMPTY_ITERATOR = new EmptyIteratorModel();
+    
+    private static class EmptyIteratorModel implements TemplateModelIterator, Serializable {
+
+        @Override
+        public TemplateModel next() throws TemplateModelException {
+            throw new TemplateModelException("The collection has no more elements.");
+        }
+
+        @Override
+        public boolean hasNext() throws TemplateModelException {
+            return false;
+        }
+        
+    }
+
+    public static final TemplateCollectionModelEx EMPTY_COLLECTION = new EmptyCollectionExModel();
+    
+    private static class EmptyCollectionExModel implements TemplateCollectionModelEx, Serializable {
+
+        @Override
+        public int size() throws TemplateModelException {
+            return 0;
+        }
+
+        @Override
+        public boolean isEmpty() throws TemplateModelException {
+            return true;
+        }
+
+        @Override
+        public TemplateModelIterator iterator() throws TemplateModelException {
+            return EMPTY_ITERATOR;
+        }
+        
+    }
+    
+    public static final TemplateSequenceModel EMPTY_SEQUENCE = new EmptySequenceModel();
+    
+    private static class EmptySequenceModel implements TemplateSequenceModel, Serializable {
+        
+        @Override
+        public TemplateModel get(int index) throws TemplateModelException {
+            return null;
+        }
+    
+        @Override
+        public int size() throws TemplateModelException {
+            return 0;
+        }
+        
+    }
+    
+    public static final TemplateHashModelEx EMPTY_HASH = new EmptyHashModel();
+    
+    private static class EmptyHashModel implements TemplateHashModelEx, Serializable {
+        
+        @Override
+        public int size() throws TemplateModelException {
+            return 0;
+        }
+
+        @Override
+        public TemplateCollectionModel keys() throws TemplateModelException {
+            return EMPTY_COLLECTION;
+        }
+
+        @Override
+        public TemplateCollectionModel values() throws TemplateModelException {
+            return EMPTY_COLLECTION;
+        }
+
+        @Override
+        public TemplateModel get(String key) throws TemplateModelException {
+            return null;
+        }
+
+        @Override
+        public boolean isEmpty() throws TemplateModelException {
+            return true;
+        }
+        
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/FalseTemplateBooleanModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/FalseTemplateBooleanModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/FalseTemplateBooleanModel.java
new file mode 100644
index 0000000..0fd848d
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/FalseTemplateBooleanModel.java
@@ -0,0 +1,36 @@
+/*
+ * 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.model;
+
+/**
+ * Used for the {@link TemplateBooleanModel#TRUE} singleton. 
+ */
+final class FalseTemplateBooleanModel implements SerializableTemplateBooleanModel {
+    
+    @Override
+    public boolean getAsBoolean() {
+        return false;
+    }
+
+    private Object readResolve() {
+        return FALSE;
+    }
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/GeneralPurposeNothing.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/GeneralPurposeNothing.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/GeneralPurposeNothing.java
new file mode 100644
index 0000000..da1c102
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/GeneralPurposeNothing.java
@@ -0,0 +1,83 @@
+/*
+ * 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.model;
+
+import java.util.List;
+
+/**
+ * Singleton object representing nothing, used by ?if_exists built-in.
+ * It is meant to be interpreted in the most sensible way possible in various contexts.
+ * This can be returned to avoid exceptions.
+ */
+
+final class GeneralPurposeNothing
+implements TemplateBooleanModel, TemplateScalarModel, TemplateSequenceModel, TemplateHashModelEx, TemplateMethodModelEx {
+
+    public static final TemplateModel INSTANCE = new GeneralPurposeNothing();
+      
+    private GeneralPurposeNothing() {
+    }
+
+    @Override
+    public String getAsString() {
+        return "";
+    }
+
+    @Override
+    public boolean getAsBoolean() {
+        return false;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return true;
+    }
+
+    @Override
+    public int size() {
+        return 0;
+    }
+
+    @Override
+    public TemplateModel get(int i) throws TemplateModelException {
+        throw new TemplateModelException("Empty list");
+    }
+
+    @Override
+    public TemplateModel get(String key) {
+        return null;
+    }
+
+    @Override
+    public Object exec(List args) {
+        return null;
+    }
+    
+    @Override
+    public TemplateCollectionModel keys() {
+        return Constants.EMPTY_COLLECTION;
+    }
+
+    @Override
+    public TemplateCollectionModel values() {
+        return Constants.EMPTY_COLLECTION;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/ObjectWrapper.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/ObjectWrapper.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/ObjectWrapper.java
new file mode 100644
index 0000000..42f09d8
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/ObjectWrapper.java
@@ -0,0 +1,59 @@
+/*
+ * 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.model;
+
+import java.util.Map;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
+
+/**
+ * Maps Java objects to the type-system of FreeMarker Template Language (see the {@link TemplateModel}
+ * interfaces). Thus this is what decides what parts of the Java objects will be accessible in the templates and how.
+ * 
+ * <p>For example, with a {@link DefaultObjectWrapper} both the items of {@link Map} and the JavaBean properties (the getters)
+ * of an object are accessible in template uniformly with the {@code myObject.foo} syntax, where "foo" is the map key or
+ * the property name. This is because both kind of object is wrapped by {@link DefaultObjectWrapper} into a
+ * {@link TemplateHashModel} implementation that will call {@link Map#get(Object)} or the getter method, transparently
+ * to the template language.
+ * 
+ * @see Configuration#getObjectWrapper()
+ */
+public interface ObjectWrapper {
+    
+    /**
+     * Makes a {@link TemplateModel} out of a non-{@link TemplateModel} object, usually by "wrapping" it into a
+     * {@link TemplateModel} implementation that delegates to the original object.
+     * 
+     * @param obj The object to wrap into a {@link TemplateModel}. If it already implements {@link TemplateModel},
+     *      it should just return the object as is. If it's {@code null}, the method should return {@code null}
+     *      (however, {@link DefaultObjectWrapper}, has a legacy option for returning a null model object instead, but it's not
+     *      a good idea).
+     * 
+     * @return a {@link TemplateModel} wrapper of the object passed in. To support un-wrapping, you may consider the
+     *     return value to implement {@link WrapperTemplateModel} and {@link AdapterTemplateModel}.  
+     *     It's normally expectated that the {@link TemplateModel} isn't less thread safe than the wrapped object.
+     *     If the {@link ObjectWrapper} returns less thread safe objects that should be clearly documented, as it
+     *     restricts how it can be used, like, then it can't be used to wrap
+     *     {@linkplain Configuration#getSharedVariables() shared variables}).
+     */
+    TemplateModel wrap(Object obj) throws TemplateModelException;
+    
+}



Mime
View raw message