/*
* 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 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
edu.
umd.
cs.
findbugs.
annotations.
SuppressFBWarnings;
import freemarker.core.
DebugBreak;
import freemarker.core.
Environment;
import freemarker.core.
TemplateElement;
import freemarker.core.
_CoreAPI;
import freemarker.debug.
Breakpoint;
import freemarker.debug.
DebuggerListener;
import freemarker.debug.
EnvironmentSuspendedEvent;
import freemarker.template.
Template;
import freemarker.template.utility.
UndeclaredThrowableException;
/**
* @version $Id
*/
class
RmiDebuggerService
extends
DebuggerService {
private final
Map templateDebugInfos = new
HashMap();
private final
HashSet suspendedEnvironments = new
HashSet();
private final
Map listeners = new
HashMap();
private final
ReferenceQueue refQueue = new
ReferenceQueue();
private final
RmiDebuggerImpl debugger;
private
DebuggerServer server;
RmiDebuggerService() {
try {
debugger = new
RmiDebuggerImpl(this);
server = new
DebuggerServer((
Serializable)
RemoteObject.
toStub(
debugger));
server.
start();
} catch (
RemoteException e) {
e.
printStackTrace();
throw new
UndeclaredThrowableException(
e);
}
}
@
Override
List getBreakpointsSpi(
String templateName) {
synchronized (
templateDebugInfos) {
TemplateDebugInfo tdi =
findTemplateDebugInfo(
templateName);
return
tdi == null ?
Collections.
EMPTY_LIST :
tdi.
breakpoints;
}
}
List getBreakpointsSpi() {
List sumlist = new
ArrayList();
synchronized (
templateDebugInfos) {
for (
Iterator iter =
templateDebugInfos.
values().
iterator();
iter.
hasNext(); ) {
sumlist.
addAll(((
TemplateDebugInfo)
iter.
next()).
breakpoints);
}
}
Collections.
sort(
sumlist);
return
sumlist;
}
// TODO See in SuppressFBWarnings
@
Override
@
SuppressFBWarnings(
value={ "UW_UNCOND_WAIT", "WA_NOT_IN_LOOP" },
justification="Will have to be re-desigend; postponed.")
boolean
suspendEnvironmentSpi(
Environment env,
String templateName, int
line)
throws
RemoteException {
RmiDebuggedEnvironmentImpl denv =
(
RmiDebuggedEnvironmentImpl)
RmiDebuggedEnvironmentImpl.
getCachedWrapperFor(
env);
synchronized (
suspendedEnvironments) {
suspendedEnvironments.
add(
denv);
}
try {
EnvironmentSuspendedEvent breakpointEvent =
new
EnvironmentSuspendedEvent(this,
templateName,
line,
denv);
synchronized (
listeners) {
for (
Iterator iter =
listeners.
values().
iterator();
iter.
hasNext(); ) {
DebuggerListener listener = (
DebuggerListener)
iter.
next();
listener.
environmentSuspended(
breakpointEvent);
}
}
synchronized (
denv) {
try {
denv.
wait();
} catch (
InterruptedException e) {
;// Intentionally ignored
}
}
return
denv.
isStopped();
} finally {
synchronized (
suspendedEnvironments) {
suspendedEnvironments.
remove(
denv);
}
}
}
@
Override
void
registerTemplateSpi(
Template template) {
String templateName =
template.
getName();
synchronized (
templateDebugInfos) {
TemplateDebugInfo tdi =
createTemplateDebugInfo(
templateName);
tdi.
templates.
add(new
TemplateReference(
templateName,
template,
refQueue));
// Inject already defined breakpoints into the template
for (
Iterator iter =
tdi.
breakpoints.
iterator();
iter.
hasNext(); ) {
Breakpoint breakpoint = (
Breakpoint)
iter.
next();
insertDebugBreak(
template,
breakpoint);
}
}
}
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 {
insertDebugBreak(
t,
breakpoint);
}
}
}
}
}
private static void
insertDebugBreak(
Template t,
Breakpoint breakpoint) {
TemplateElement te =
findTemplateElement(
t.
getRootTreeNode(),
breakpoint.
getLine());
if (
te == null) {
return;
}
TemplateElement parent =
_CoreAPI.
getParentElement(
te);
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 =
_CoreAPI.
getParentElement(
te);
}
if (
db == null) {
return;
}
TemplateElement parent =
_CoreAPI.
getParentElement(
db);
parent.
setChildAt(
parent.
getIndex(
db),
_CoreAPI.
getChildElement(
db, 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 =
_CoreAPI.
getChildElement(
te,
i);
while (
child instanceof
DebugBreak) {
TemplateElement dbchild =
_CoreAPI.
getChildElement(
child, 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);
}
}
}
}
@
Override
void
shutdownSpi() {
server.
stop();
try {
UnicastRemoteObject.
unexportObject(this.
debugger, true);
} catch (
Exception e) {
}
RmiDebuggedEnvironmentImpl.
cleanup();
}
}