001: /*
002: * Copyright (c) 2003 The Visigoth Software Society. All rights
003: * reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions
007: * are met:
008: *
009: * 1. Redistributions of source code must retain the above copyright
010: * notice, this list of conditions and the following disclaimer.
011: *
012: * 2. Redistributions in binary form must reproduce the above copyright
013: * notice, this list of conditions and the following disclaimer in
014: * the documentation and/or other materials provided with the
015: * distribution.
016: *
017: * 3. The end-user documentation included with the redistribution, if
018: * any, must include the following acknowledgement:
019: * "This product includes software developed by the
020: * Visigoth Software Society (http://www.visigoths.org/)."
021: * Alternately, this acknowledgement may appear in the software itself,
022: * if and wherever such third-party acknowledgements normally appear.
023: *
024: * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
025: * project contributors may be used to endorse or promote products derived
026: * from this software without prior written permission. For written
027: * permission, please contact visigoths@visigoths.org.
028: *
029: * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
030: * nor may "FreeMarker" or "Visigoth" appear in their names
031: * without prior written permission of the Visigoth Software Society.
032: *
033: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
034: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
035: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
036: * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
037: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
038: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
039: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
040: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
041: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
042: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
043: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
044: * SUCH DAMAGE.
045: * ====================================================================
046: *
047: * This software consists of voluntary contributions made by many
048: * individuals on behalf of the Visigoth Software Society. For more
049: * information on the Visigoth Software Society, please see
050: * http://www.visigoths.org/
051: */
052:
053: package freemarker.debug.impl;
054:
055: import java.io.Serializable;
056: import java.lang.ref.ReferenceQueue;
057: import java.lang.ref.WeakReference;
058: import java.rmi.RemoteException;
059: import java.rmi.server.RemoteObject;
060: import java.util.ArrayList;
061: import java.util.Collection;
062: import java.util.Collections;
063: import java.util.Enumeration;
064: import java.util.HashMap;
065: import java.util.HashSet;
066: import java.util.Iterator;
067: import java.util.List;
068: import java.util.Map;
069:
070: import freemarker.core.DebugBreak;
071: import freemarker.core.Environment;
072: import freemarker.core.TemplateElement;
073: import freemarker.debug.Breakpoint;
074: import freemarker.debug.DebuggerListener;
075: import freemarker.debug.EnvironmentSuspendedEvent;
076: import freemarker.template.Template;
077: import freemarker.template.utility.UndeclaredThrowableException;
078:
079: /**
080: * @author Attila Szegedi
081: * @version $Id
082: */
083: class RmiDebuggerService extends DebuggerService {
084: private final Map templateDebugInfos = new HashMap();
085: private final HashSet suspendedEnvironments = new HashSet();
086: private final Map listeners = new HashMap();
087: private final ReferenceQueue refQueue = new ReferenceQueue();
088:
089: RmiDebuggerService() {
090: try {
091: new DebuggerServer((Serializable) RemoteObject
092: .toStub(new RmiDebuggerImpl(this ))).start();
093: } catch (RemoteException e) {
094: e.printStackTrace();
095: throw new UndeclaredThrowableException(e);
096: }
097: }
098:
099: List getBreakpointsSpi(String templateName) {
100: synchronized (templateDebugInfos) {
101: TemplateDebugInfo tdi = findTemplateDebugInfo(templateName);
102: return tdi == null ? Collections.EMPTY_LIST
103: : tdi.breakpoints;
104: }
105: }
106:
107: List getBreakpointsSpi() {
108: List sumlist = new ArrayList();
109: synchronized (templateDebugInfos) {
110: for (Iterator iter = templateDebugInfos.values().iterator(); iter
111: .hasNext();) {
112: sumlist
113: .addAll(((TemplateDebugInfo) iter.next()).breakpoints);
114: }
115: }
116: Collections.sort(sumlist);
117: return sumlist;
118: }
119:
120: boolean suspendEnvironmentSpi(Environment env, int line)
121: throws RemoteException {
122: RmiDebuggedEnvironmentImpl denv = (RmiDebuggedEnvironmentImpl) RmiDebuggedEnvironmentImpl
123: .getCachedWrapperFor(env);
124:
125: synchronized (suspendedEnvironments) {
126: suspendedEnvironments.add(denv);
127: }
128: try {
129: EnvironmentSuspendedEvent breakpointEvent = new EnvironmentSuspendedEvent(
130: this , line, denv);
131:
132: synchronized (listeners) {
133: for (Iterator iter = listeners.values().iterator(); iter
134: .hasNext();) {
135: DebuggerListener listener = (DebuggerListener) iter
136: .next();
137: listener.environmentSuspended(breakpointEvent);
138: }
139: }
140: synchronized (denv) {
141: try {
142: denv.wait();
143: } catch (InterruptedException e) {
144: ;// Intentionally ignored
145: }
146: }
147: return denv.isStopped();
148: } finally {
149: synchronized (suspendedEnvironments) {
150: suspendedEnvironments.remove(denv);
151: }
152: }
153: }
154:
155: void registerTemplateSpi(Template template) {
156: String templateName = template.getName();
157: synchronized (templateDebugInfos) {
158: TemplateDebugInfo tdi = createTemplateDebugInfo(templateName);
159: tdi.templates.add(new TemplateReference(templateName,
160: template, refQueue));
161: // Inject already defined breakpoints into the template
162: for (Iterator iter = tdi.breakpoints.iterator(); iter
163: .hasNext();) {
164: Breakpoint breakpoint = (Breakpoint) iter.next();
165: insertDebugBreak(template, breakpoint);
166: }
167: }
168: }
169:
170: Collection getSuspendedEnvironments() {
171: return (Collection) suspendedEnvironments.clone();
172: }
173:
174: Object addDebuggerListener(DebuggerListener listener) {
175: Object id;
176: synchronized (listeners) {
177: id = new Long(System.currentTimeMillis());
178: listeners.put(id, listener);
179: }
180: return id;
181: }
182:
183: void removeDebuggerListener(Object id) {
184: synchronized (listeners) {
185: listeners.remove(id);
186: }
187: }
188:
189: void addBreakpoint(Breakpoint breakpoint) {
190: String templateName = breakpoint.getTemplateName();
191: synchronized (templateDebugInfos) {
192: TemplateDebugInfo tdi = createTemplateDebugInfo(templateName);
193: List breakpoints = tdi.breakpoints;
194: int pos = Collections.binarySearch(breakpoints, breakpoint);
195: if (pos < 0) {
196: // Add to the list of breakpoints
197: breakpoints.add(-pos - 1, breakpoint);
198: // Inject the breakpoint into all templates with this name
199: for (Iterator iter = tdi.templates.iterator(); iter
200: .hasNext();) {
201: TemplateReference ref = (TemplateReference) iter
202: .next();
203: Template t = ref.getTemplate();
204: if (t == null) {
205: iter.remove();
206: } else {
207: insertDebugBreak(t, breakpoint);
208: }
209: }
210: }
211: }
212: }
213:
214: private static void insertDebugBreak(Template t,
215: Breakpoint breakpoint) {
216: TemplateElement te = findTemplateElement(t.getRootTreeNode(),
217: breakpoint.getLine());
218: if (te == null) {
219: return;
220: }
221: TemplateElement parent = (TemplateElement) te.getParent();
222: DebugBreak db = new DebugBreak(te);
223: // TODO: Ensure there always is a parent by making sure
224: // that the root element in the template is always a MixedContent
225: // Also make sure it doesn't conflict with anyone's code.
226: parent.setChildAt(parent.getIndex(te), db);
227: }
228:
229: private static TemplateElement findTemplateElement(
230: TemplateElement te, int line) {
231: if (te.getBeginLine() > line || te.getEndLine() < line) {
232: return null;
233: }
234: // Find the narrowest match
235: for (Enumeration children = te.children(); children
236: .hasMoreElements();) {
237: TemplateElement child = (TemplateElement) children
238: .nextElement();
239: TemplateElement childmatch = findTemplateElement(child,
240: line);
241: if (childmatch != null) {
242: return childmatch;
243: }
244: }
245: // If no child provides narrower match, return this
246: return te;
247: }
248:
249: private TemplateDebugInfo findTemplateDebugInfo(String templateName) {
250: processRefQueue();
251: return (TemplateDebugInfo) templateDebugInfos.get(templateName);
252: }
253:
254: private TemplateDebugInfo createTemplateDebugInfo(
255: String templateName) {
256: TemplateDebugInfo tdi = findTemplateDebugInfo(templateName);
257: if (tdi == null) {
258: tdi = new TemplateDebugInfo();
259: templateDebugInfos.put(templateName, tdi);
260: }
261: return tdi;
262: }
263:
264: void removeBreakpoint(Breakpoint breakpoint) {
265: String templateName = breakpoint.getTemplateName();
266: synchronized (templateDebugInfos) {
267: TemplateDebugInfo tdi = findTemplateDebugInfo(templateName);
268: if (tdi != null) {
269: List breakpoints = tdi.breakpoints;
270: int pos = Collections.binarySearch(breakpoints,
271: breakpoint);
272: if (pos >= 0) {
273: breakpoints.remove(pos);
274: for (Iterator iter = tdi.templates.iterator(); iter
275: .hasNext();) {
276: TemplateReference ref = (TemplateReference) iter
277: .next();
278: Template t = ref.getTemplate();
279: if (t == null) {
280: iter.remove();
281: } else {
282: removeDebugBreak(t, breakpoint);
283: }
284: }
285: }
286: if (tdi.isEmpty()) {
287: templateDebugInfos.remove(templateName);
288: }
289: }
290: }
291: }
292:
293: private void removeDebugBreak(Template t, Breakpoint breakpoint) {
294: TemplateElement te = findTemplateElement(t.getRootTreeNode(),
295: breakpoint.getLine());
296: if (te == null) {
297: return;
298: }
299: DebugBreak db = null;
300: while (te != null) {
301: if (te instanceof DebugBreak) {
302: db = (DebugBreak) te;
303: break;
304: }
305: te = (TemplateElement) te.getParent();
306: }
307: if (db == null) {
308: return;
309: }
310: TemplateElement parent = (TemplateElement) db.getParent();
311: parent.setChildAt(parent.getIndex(db), (TemplateElement) db
312: .getChildAt(0));
313: }
314:
315: void removeBreakpoints(String templateName) {
316: synchronized (templateDebugInfos) {
317: TemplateDebugInfo tdi = findTemplateDebugInfo(templateName);
318: if (tdi != null) {
319: removeBreakpoints(tdi);
320: if (tdi.isEmpty()) {
321: templateDebugInfos.remove(templateName);
322: }
323: }
324: }
325: }
326:
327: void removeBreakpoints() {
328: synchronized (templateDebugInfos) {
329: for (Iterator iter = templateDebugInfos.values().iterator(); iter
330: .hasNext();) {
331: TemplateDebugInfo tdi = (TemplateDebugInfo) iter.next();
332: removeBreakpoints(tdi);
333: if (tdi.isEmpty()) {
334: iter.remove();
335: }
336: }
337: }
338: }
339:
340: private void removeBreakpoints(TemplateDebugInfo tdi) {
341: tdi.breakpoints.clear();
342: for (Iterator iter = tdi.templates.iterator(); iter.hasNext();) {
343: TemplateReference ref = (TemplateReference) iter.next();
344: Template t = ref.getTemplate();
345: if (t == null) {
346: iter.remove();
347: } else {
348: removeDebugBreaks(t.getRootTreeNode());
349: }
350: }
351: }
352:
353: private void removeDebugBreaks(TemplateElement te) {
354: int count = te.getChildCount();
355: for (int i = 0; i < count; ++i) {
356: TemplateElement child = (TemplateElement) te.getChildAt(i);
357: while (child instanceof DebugBreak) {
358: TemplateElement dbchild = (TemplateElement) child
359: .getChildAt(0);
360: te.setChildAt(i, dbchild);
361: child = dbchild;
362: }
363: removeDebugBreaks(child);
364: }
365: }
366:
367: private static final class TemplateDebugInfo {
368: final List templates = new ArrayList();
369: final List breakpoints = new ArrayList();
370:
371: boolean isEmpty() {
372: return templates.isEmpty() && breakpoints.isEmpty();
373: }
374: }
375:
376: private static final class TemplateReference extends WeakReference {
377: final String templateName;
378:
379: TemplateReference(String templateName, Template template,
380: ReferenceQueue queue) {
381: super (template, queue);
382: this .templateName = templateName;
383: }
384:
385: Template getTemplate() {
386: return (Template) get();
387: }
388: }
389:
390: private void processRefQueue() {
391: for (;;) {
392: TemplateReference ref = (TemplateReference) refQueue.poll();
393: if (ref == null) {
394: break;
395: }
396: TemplateDebugInfo tdi = findTemplateDebugInfo(ref.templateName);
397: if (tdi != null) {
398: tdi.templates.remove(ref);
399: if (tdi.isEmpty()) {
400: templateDebugInfos.remove(ref.templateName);
401: }
402: }
403: }
404: }
405: }
|