Merge branch '2.3-gae' into 2.3-gae-tools
diff --git a/src/main/java/freemarker/core/DebugBreak.java b/src/main/java/freemarker/core/DebugBreak.java
index ead0654..1de26f5 100644
--- a/src/main/java/freemarker/core/DebugBreak.java
+++ b/src/main/java/freemarker/core/DebugBreak.java
@@ -53,8 +53,9 @@
package freemarker.core;
import java.io.IOException;
+import java.io.Serializable;
-import freemarker.debug.impl.DebuggerService;
+import freemarker.debug.DebuggerService;
import freemarker.template.TemplateException;
/**
@@ -62,16 +63,21 @@
*/
public class DebugBreak extends TemplateElement
{
- public DebugBreak(TemplateElement nestedBlock)
+ private final DebuggerService debuggerService;
+ private final Serializable data;
+
+ public DebugBreak(TemplateElement nestedBlock, DebuggerService debuggerService, Serializable data)
{
this.nestedBlock = nestedBlock;
nestedBlock.parent = this;
copyLocationFrom(nestedBlock);
+ this.debuggerService = debuggerService;
+ this.data = data;
}
protected void accept(Environment env) throws TemplateException, IOException
{
- if(!DebuggerService.suspendEnvironment(env, this.getTemplate().getName(), nestedBlock.getBeginLine()))
+ if(!debuggerService.suspendEnvironment(env, getTemplate().getName(), nestedBlock.getBeginLine(), data))
{
nestedBlock.accept(env);
}
diff --git a/src/main/java/freemarker/debug/Breakpoint.java b/src/main/java/freemarker/debug/Breakpoint.java
index d54e910..d12c7cc 100644
--- a/src/main/java/freemarker/debug/Breakpoint.java
+++ b/src/main/java/freemarker/debug/Breakpoint.java
@@ -64,16 +64,28 @@
private final String templateName;
private final int line;
+ private final Serializable data;
+
+ public Breakpoint(String templateName, int line) {
+ this(templateName, line, null);
+ }
/**
* Creates a new breakpoint.
* @param templateName the name of the template
* @param line the line number in the template where to put the breakpoint
+ * @param data arbitrary data attached to this breakpoint, that the debugger client will get back with the
+ * suspension event. As this data will travel back and forth between the client and server, <b>it has to be
+ * de-serializable on the server</b>. Also, it shouldn't be very big serialized, as it will be possibly sent
+ * through network. So if the data is big or might not be de-serializable on another JVM (missing classes),
+ * the client should just use an identifier as data (like a {@link Long}) with which later it can get
+ * the actual data that's only stored on the client.
*/
- public Breakpoint(String templateName, int line)
+ public Breakpoint(String templateName, int line, Serializable data)
{
this.templateName = templateName;
this.line = line;
+ this.data = data;
}
/**
@@ -83,6 +95,7 @@
{
return line;
}
+
/**
* Returns the template name of the breakpoint
*/
@@ -91,6 +104,14 @@
return templateName;
}
+ /**
+ * Returns the arbitrary data attached to this breakpoint.
+ */
+ public Serializable getData()
+ {
+ return data;
+ }
+
public int hashCode()
{
return templateName.hashCode() + 31 * line;
diff --git a/src/main/java/freemarker/debug/DebuggerService.java b/src/main/java/freemarker/debug/DebuggerService.java
new file mode 100644
index 0000000..dd9a344
--- /dev/null
+++ b/src/main/java/freemarker/debug/DebuggerService.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2003 The Visigoth Software Society. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ * any, must include the following acknowledgement:
+ * "This product includes software developed by the
+ * Visigoth Software Society (http://www.visigoths.org/)."
+ * Alternately, this acknowledgement may appear in the software itself,
+ * if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
+ * project contributors may be used to endorse or promote products derived
+ * from this software without prior written permission. For written
+ * permission, please contact visigoths@visigoths.org.
+ *
+ * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
+ * nor may "FreeMarker" or "Visigoth" appear in their names
+ * without prior written permission of the Visigoth Software Society.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Visigoth Software Society. For more
+ * information on the Visigoth Software Society, please see
+ * http://www.visigoths.org/
+ */
+
+package freemarker.debug;
+
+import java.io.Serializable;
+import java.rmi.RemoteException;
+
+import freemarker.core.Environment;
+import freemarker.template.Template;
+
+/**
+ * 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.
+ *
+ * @author Attila Szegedi
+ * @version $Id: DebuggerService.java,v 1.2 2003/05/30 16:51:53 szegedia Exp $
+ */
+public interface DebuggerService {
+
+ void addBreakpoint(Breakpoint breakpoint);
+
+ void removeBreakpoint(Breakpoint breakpoint);
+
+ boolean suspendEnvironment(Environment env, String templateName, int line, Serializable data) throws
+ RemoteException;
+
+ void registerTemplate(Template template);
+
+ void removeDebuggerListener(Object id);
+
+ Object addDebuggerListener(DebuggerListener listener);
+
+ void shutdown();
+
+}
\ No newline at end of file
diff --git a/src/main/java/freemarker/debug/EnvironmentSuspendedEvent.java b/src/main/java/freemarker/debug/EnvironmentSuspendedEvent.java
index 360bb66..b9bc2cc 100644
--- a/src/main/java/freemarker/debug/EnvironmentSuspendedEvent.java
+++ b/src/main/java/freemarker/debug/EnvironmentSuspendedEvent.java
@@ -1,5 +1,6 @@
package freemarker.debug;
+import java.io.Serializable;
import java.util.EventObject;
/**
@@ -13,13 +14,15 @@
private final String name;
private final int line;
+ private final Serializable data;
private final DebuggedEnvironment env;
- public EnvironmentSuspendedEvent(Object source, String templateName, int line, DebuggedEnvironment env)
+ public EnvironmentSuspendedEvent(Object source, String templateName, int line, Serializable data, DebuggedEnvironment env)
{
super(source);
this.name = templateName;
this.line = line;
+ this.data = data;
this.env = env;
}
@@ -42,6 +45,14 @@
{
return line;
}
+
+ /**
+ * Returns the arbitrary data that the debugger clients has attached to the {@link Breakpoint};
+ * possibly {@code null}.
+ */
+ public Serializable getData() {
+ return data;
+ }
/**
* The environment that was suspended
diff --git a/src/main/java/freemarker/debug/impl/AbstractDebuggerService.java b/src/main/java/freemarker/debug/impl/AbstractDebuggerService.java
new file mode 100644
index 0000000..0c55cb4
--- /dev/null
+++ b/src/main/java/freemarker/debug/impl/AbstractDebuggerService.java
@@ -0,0 +1,484 @@
+/*
+ * Copyright (c) 2003 The Visigoth Software Society. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ * any, must include the following acknowledgement:
+ * "This product includes software developed by the
+ * Visigoth Software Society (http://www.visigoths.org/)."
+ * Alternately, this acknowledgement may appear in the software itself,
+ * if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
+ * project contributors may be used to endorse or promote products derived
+ * from this software without prior written permission. For written
+ * permission, please contact visigoths@visigoths.org.
+ *
+ * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
+ * nor may "FreeMarker" or "Visigoth" appear in their names
+ * without prior written permission of the Visigoth Software Society.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Visigoth Software Society. For more
+ * information on the Visigoth Software Society, please see
+ * http://www.visigoths.org/
+ */
+
+package freemarker.debug.impl;
+
+import java.io.Serializable;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.rmi.RemoteException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import freemarker.core.DebugBreak;
+import freemarker.core.Environment;
+import freemarker.core.TemplateElement;
+import freemarker.debug.Breakpoint;
+import freemarker.debug.DebuggerListener;
+import freemarker.debug.DebuggerService;
+import freemarker.debug.EnvironmentSuspendedEvent;
+import freemarker.template.Template;
+
+/**
+ * @author Attila Szegedi
+ * @version $Id
+ */
+public abstract class AbstractDebuggerService
+implements
+ 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();
+
+ public List getBreakpoints(String templateName)
+ {
+ synchronized(templateDebugInfos)
+ {
+ TemplateDebugInfo tdi = findTemplateDebugInfo(templateName);
+ return tdi == null ? Collections.EMPTY_LIST : tdi.breakpoints;
+ }
+ }
+
+ public List getBreakpoints()
+ {
+ 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;
+ }
+
+ public boolean suspendEnvironment(Environment env, String templateName, int line, Serializable data)
+ throws
+ RemoteException
+ {
+ RmiDebuggedEnvironmentImpl denv =
+ (RmiDebuggedEnvironmentImpl)
+ RmiDebuggedEnvironmentImpl.getCachedWrapperFor(env);
+
+ synchronized(suspendedEnvironments)
+ {
+ suspendedEnvironments.add(denv);
+ }
+ try
+ {
+ EnvironmentSuspendedEvent breakpointEvent =
+ new EnvironmentSuspendedEvent(this, templateName, line, data, 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);
+ }
+ }
+ }
+
+ public void registerTemplate(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();
+ insertDebugBreak(template, breakpoint);
+ }
+ }
+ }
+
+ Collection getSuspendedEnvironments()
+ {
+ return (Collection)suspendedEnvironments.clone();
+ }
+
+ public Object addDebuggerListener(DebuggerListener listener)
+ {
+ Object id;
+ synchronized(listeners)
+ {
+ id = new Long(System.currentTimeMillis());
+ listeners.put(id, listener);
+ }
+ return id;
+ }
+
+ public void removeDebuggerListener(Object id)
+ {
+ synchronized(listeners)
+ {
+ listeners.remove(id);
+ }
+ }
+
+ public 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
+ {
+ insertDebugBreak(t, breakpoint);
+ }
+ }
+ }
+ }
+ }
+
+ private void insertDebugBreak(Template t, Breakpoint breakpoint)
+ {
+ TemplateElement te = findTemplateElement(t.getRootTreeNode(), breakpoint.getLine());
+ if(te == null)
+ {
+ return;
+ }
+ TemplateElement parent = (TemplateElement)te.getParent();
+ DebugBreak db = new DebugBreak(te, this, breakpoint.getData());
+ // TODO: Ensure there always is a parent by making sure
+ // that the root element in the template is always a MixedContent
+ // Also make sure it doesn't conflict with anyone's code.
+ parent.setChildAt(parent.getIndex(te), db);
+ }
+
+ private static TemplateElement findTemplateElement(TemplateElement te, int line)
+ {
+ if(te.getBeginLine() > line || te.getEndLine() < line)
+ {
+ return null;
+ }
+ // Find the narrowest match
+ List childMatches = new ArrayList();
+ for(Enumeration children = te.children(); children.hasMoreElements();)
+ {
+ TemplateElement child = (TemplateElement)children.nextElement();
+ TemplateElement childmatch = findTemplateElement(child, line);
+ if(childmatch != null)
+ {
+ childMatches.add(childmatch);
+ }
+ }
+ //find a match that exactly matches the begin/end line
+ TemplateElement bestMatch = null;
+ for(int i = 0; i < childMatches.size(); i++)
+ {
+ TemplateElement e = (TemplateElement) childMatches.get(i);
+
+ if( bestMatch == null )
+ {
+ bestMatch = e;
+ }
+
+ if( e.getBeginLine() == line && e.getEndLine() > line )
+ {
+ bestMatch = e;
+ }
+
+ if( e.getBeginLine() == e.getEndLine() && e.getBeginLine() == line)
+ {
+ bestMatch = e;
+ break;
+ }
+ }
+ if( bestMatch != null)
+ {
+ return bestMatch;
+ }
+ // If no child provides narrower match, return this
+ return te;
+ }
+
+ 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;
+ }
+
+ public 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
+ {
+ removeDebugBreak(t, breakpoint);
+ }
+ }
+ }
+ if(tdi.isEmpty())
+ {
+ templateDebugInfos.remove(templateName);
+ }
+ }
+ }
+ }
+
+ private void removeDebugBreak(Template t, Breakpoint breakpoint)
+ {
+ TemplateElement te = findTemplateElement(t.getRootTreeNode(), breakpoint.getLine());
+ if(te == null)
+ {
+ return;
+ }
+ DebugBreak db = null;
+ while(te != null)
+ {
+ if(te instanceof DebugBreak)
+ {
+ db = (DebugBreak)te;
+ break;
+ }
+ te = (TemplateElement)te.getParent();
+ }
+ if(db == null)
+ {
+ return;
+ }
+ TemplateElement parent = (TemplateElement)db.getParent();
+ parent.setChildAt(parent.getIndex(db), (TemplateElement)db.getChildAt(0));
+ }
+
+ 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
+ {
+ removeDebugBreaks(t.getRootTreeNode());
+ }
+ }
+ }
+
+ private void removeDebugBreaks(TemplateElement te)
+ {
+ int count = te.getChildCount();
+ for(int i = 0; i < count; ++i)
+ {
+ TemplateElement child = (TemplateElement)te.getChildAt(i);
+ while(child instanceof DebugBreak)
+ {
+ TemplateElement dbchild = (TemplateElement)child.getChildAt(0);
+ te.setChildAt(i, dbchild);
+ child = dbchild;
+ }
+ removeDebugBreaks(child);
+ }
+ }
+
+ 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);
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/freemarker/debug/impl/DebuggerService.java b/src/main/java/freemarker/debug/impl/DebuggerService.java
deleted file mode 100644
index c64762d..0000000
--- a/src/main/java/freemarker/debug/impl/DebuggerService.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (c) 2003 The Visigoth Software Society. All rights
- * reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- *
- * 3. The end-user documentation included with the redistribution, if
- * any, must include the following acknowledgement:
- * "This product includes software developed by the
- * Visigoth Software Society (http://www.visigoths.org/)."
- * Alternately, this acknowledgement may appear in the software itself,
- * if and wherever such third-party acknowledgements normally appear.
- *
- * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
- * project contributors may be used to endorse or promote products derived
- * from this software without prior written permission. For written
- * permission, please contact visigoths@visigoths.org.
- *
- * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
- * nor may "FreeMarker" or "Visigoth" appear in their names
- * without prior written permission of the Visigoth Software Society.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
- * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Visigoth Software Society. For more
- * information on the Visigoth Software Society, please see
- * http://www.visigoths.org/
- */
-
-package freemarker.debug.impl;
-
-import java.rmi.RemoteException;
-import java.util.Collections;
-import java.util.List;
-
-import freemarker.core.Environment;
-import freemarker.template.Template;
-import freemarker.template.utility.SecurityUtilities;
-
-/**
- * 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.
- * @author Attila Szegedi
- */
-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
- SecurityUtilities.getSystemProperty("freemarker.debug.password") == null
- ? (DebuggerService)new NoOpDebuggerService()
- : (DebuggerService)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
- {
- List getBreakpointsSpi(String templateName)
- {
- return Collections.EMPTY_LIST;
- }
-
- boolean suspendEnvironmentSpi(Environment env, String templateName, int line)
- {
- throw new UnsupportedOperationException();
- }
-
- void registerTemplateSpi(Template template)
- {
- }
-
- void shutdownSpi()
- {
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/java/freemarker/debug/impl/RmiDebuggerImpl.java b/src/main/java/freemarker/debug/impl/RmiDebuggerImpl.java
index 438c0b4..0e46713 100644
--- a/src/main/java/freemarker/debug/impl/RmiDebuggerImpl.java
+++ b/src/main/java/freemarker/debug/impl/RmiDebuggerImpl.java
@@ -39,12 +39,12 @@
public List getBreakpoints()
{
- return service.getBreakpointsSpi();
+ return service.getBreakpoints();
}
public List getBreakpoints(String templateName)
{
- return service.getBreakpointsSpi(templateName);
+ return service.getBreakpoints(templateName);
}
public Collection getSuspendedEnvironments()
diff --git a/src/main/java/freemarker/debug/impl/RmiDebuggerService.java b/src/main/java/freemarker/debug/impl/RmiDebuggerService.java
index 754becd..0fde850 100644
--- a/src/main/java/freemarker/debug/impl/RmiDebuggerService.java
+++ b/src/main/java/freemarker/debug/impl/RmiDebuggerService.java
@@ -53,28 +53,10 @@
package freemarker.debug.impl;
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.Enumeration;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import freemarker.core.DebugBreak;
-import freemarker.core.Environment;
-import freemarker.core.TemplateElement;
-import freemarker.debug.Breakpoint;
-import freemarker.debug.DebuggerListener;
-import freemarker.debug.EnvironmentSuspendedEvent;
-import freemarker.template.Template;
import freemarker.template.utility.UndeclaredThrowableException;
/**
@@ -83,14 +65,9 @@
*/
class RmiDebuggerService
extends
- DebuggerService
+ AbstractDebuggerService
{
- 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;
@@ -109,400 +86,7 @@
}
}
- 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;
- }
-
- 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);
- }
- }
- }
-
- 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();
- insertDebugBreak(template, breakpoint);
- }
- }
- }
-
- Collection getSuspendedEnvironments()
- {
- return (Collection)suspendedEnvironments.clone();
- }
-
- Object addDebuggerListener(DebuggerListener listener)
- {
- Object id;
- synchronized(listeners)
- {
- id = new Long(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
- {
- insertDebugBreak(t, breakpoint);
- }
- }
- }
- }
- }
-
- private static void insertDebugBreak(Template t, Breakpoint breakpoint)
- {
- TemplateElement te = findTemplateElement(t.getRootTreeNode(), breakpoint.getLine());
- if(te == null)
- {
- return;
- }
- TemplateElement parent = (TemplateElement)te.getParent();
- DebugBreak db = new DebugBreak(te);
- // TODO: Ensure there always is a parent by making sure
- // that the root element in the template is always a MixedContent
- // Also make sure it doesn't conflict with anyone's code.
- parent.setChildAt(parent.getIndex(te), db);
- }
-
- private static TemplateElement findTemplateElement(TemplateElement te, int line)
- {
- if(te.getBeginLine() > line || te.getEndLine() < line)
- {
- return null;
- }
- // Find the narrowest match
- List childMatches = new ArrayList();
- for(Enumeration children = te.children(); children.hasMoreElements();)
- {
- TemplateElement child = (TemplateElement)children.nextElement();
- TemplateElement childmatch = findTemplateElement(child, line);
- if(childmatch != null)
- {
- childMatches.add(childmatch);
- }
- }
- //find a match that exactly matches the begin/end line
- TemplateElement bestMatch = null;
- for(int i = 0; i < childMatches.size(); i++)
- {
- TemplateElement e = (TemplateElement) childMatches.get(i);
-
- if( bestMatch == null )
- {
- bestMatch = e;
- }
-
- if( e.getBeginLine() == line && e.getEndLine() > line )
- {
- bestMatch = e;
- }
-
- if( e.getBeginLine() == e.getEndLine() && e.getBeginLine() == line)
- {
- bestMatch = e;
- break;
- }
- }
- if( bestMatch != null)
- {
- return bestMatch;
- }
- // If no child provides narrower match, return this
- return te;
- }
-
- 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
- {
- removeDebugBreak(t, breakpoint);
- }
- }
- }
- if(tdi.isEmpty())
- {
- templateDebugInfos.remove(templateName);
- }
- }
- }
- }
-
- private void removeDebugBreak(Template t, Breakpoint breakpoint)
- {
- TemplateElement te = findTemplateElement(t.getRootTreeNode(), breakpoint.getLine());
- if(te == null)
- {
- return;
- }
- DebugBreak db = null;
- while(te != null)
- {
- if(te instanceof DebugBreak)
- {
- db = (DebugBreak)te;
- break;
- }
- te = (TemplateElement)te.getParent();
- }
- if(db == null)
- {
- return;
- }
- TemplateElement parent = (TemplateElement)db.getParent();
- parent.setChildAt(parent.getIndex(db), (TemplateElement)db.getChildAt(0));
- }
-
- 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
- {
- removeDebugBreaks(t.getRootTreeNode());
- }
- }
- }
-
- private void removeDebugBreaks(TemplateElement te)
- {
- int count = te.getChildCount();
- for(int i = 0; i < count; ++i)
- {
- TemplateElement child = (TemplateElement)te.getChildAt(i);
- while(child instanceof DebugBreak)
- {
- TemplateElement dbchild = (TemplateElement)child.getChildAt(0);
- te.setChildAt(i, dbchild);
- child = dbchild;
- }
- removeDebugBreaks(child);
- }
- }
-
- 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);
- }
- }
- }
- }
-
- void shutdownSpi()
+ public void shutdown()
{
server.stop();
try
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index 2004e21..bc57b4e 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -88,6 +88,7 @@
import freemarker.core._CoreAPI;
import freemarker.core._DelayedJQuote;
import freemarker.core._MiscTemplateException;
+import freemarker.debug.DebuggerService;
import freemarker.template.utility.CaptureOutput;
import freemarker.template.utility.ClassUtil;
import freemarker.template.utility.HtmlEscape;
@@ -192,6 +193,8 @@
private ArrayList autoImports = new ArrayList(), autoIncludes = new ArrayList();
private Map autoImportNsToTmpMap = new HashMap(); // TODO No need for this, instead use List<NamespaceToTemplate> below.
+ private DebuggerService debuggerService;
+
public Configuration() {
cache = new TemplateCache();
cache.setConfiguration(this);
@@ -1247,4 +1250,12 @@
}
return s;
}
+
+ public DebuggerService getDebuggerService() {
+ return debuggerService;
+ }
+
+ public void setDebuggerService(DebuggerService debuggerService) {
+ this.debuggerService = debuggerService;
+ }
}
diff --git a/src/main/java/freemarker/template/Template.java b/src/main/java/freemarker/template/Template.java
index 6589294..a64dedb 100644
--- a/src/main/java/freemarker/template/Template.java
+++ b/src/main/java/freemarker/template/Template.java
@@ -78,7 +78,6 @@
import freemarker.core.TemplateElement;
import freemarker.core.TextBlock;
import freemarker.core.TokenMgrError;
-import freemarker.debug.impl.DebuggerService;
/**
* <p>Stores an already parsed template, ready to be processed (rendered) for unlimited times, possibly from
@@ -207,10 +206,17 @@
finally {
reader.close();
}
- DebuggerService.registerTemplate(this);
+ registerTemplateToDebugger(this, cfg);
namespaceURIToPrefixLookup = Collections.unmodifiableMap(namespaceURIToPrefixLookup);
prefixToNamespaceURILookup = Collections.unmodifiableMap(prefixToNamespaceURILookup);
}
+
+ static void registerTemplateToDebugger(Template template, Configuration cfg) {
+ freemarker.debug.DebuggerService debuggerService = cfg.getDebuggerService();
+ if (debuggerService != null) {
+ debuggerService.registerTemplate(template);
+ }
+ }
/**
* Equivalent to {@link #Template(String, Reader, Configuration)
@@ -230,7 +236,7 @@
Template(String name, TemplateElement root, Configuration config) {
this(name, config);
this.rootElement = root;
- DebuggerService.registerTemplate(this);
+ registerTemplateToDebugger(this, config);
}
/**
@@ -246,7 +252,7 @@
Template template = new Template(name, config);
TextBlock block = new TextBlock(content);
template.rootElement = block;
- DebuggerService.registerTemplate(template);
+ registerTemplateToDebugger(template, config);
return template;
}