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; + +}
