blob: 8bb1b3c1077ff90a9d5404728be7a1a5ce4d6db6 [file] [log] [blame]
/*
* 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.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.Iterator;
import java.util.List;
import java.util.Set;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import freemarker.cache.CacheStorage;
import freemarker.cache.SoftCacheStorage;
import freemarker.core.Configurable;
import freemarker.core.Environment;
import freemarker.debug.DebugModel;
import freemarker.debug.DebuggedEnvironment;
import freemarker.ext.util.IdentityHashMap;
import freemarker.template.Configuration;
import freemarker.template.SimpleCollection;
import freemarker.template.SimpleScalar;
import freemarker.template.Template;
import freemarker.template.TemplateCollectionModel;
import freemarker.template.TemplateHashModelEx;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.utility.UndeclaredThrowableException;
/**
*/
class RmiDebuggedEnvironmentImpl
extends
RmiDebugModelImpl
implements
DebuggedEnvironment {
private static final long serialVersionUID = 1L;
private static final CacheStorage storage = new SoftCacheStorage(new IdentityHashMap());
private static final Object idLock = new Object();
private static long nextId = 1;
private static Set remotes = new HashSet();
private boolean stopped = false;
private final long id;
private RmiDebuggedEnvironmentImpl(Environment env) throws RemoteException {
super(new DebugEnvironmentModel(env), DebugModel.TYPE_ENVIRONMENT);
synchronized (idLock) {
id = nextId++;
}
}
static synchronized Object getCachedWrapperFor(Object key)
throws RemoteException {
Object value = storage.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) {
storage.put(key, value);
}
if (value instanceof Remote) {
remotes.add(value);
}
return value;
}
// TODO See in SuppressFBWarnings
@SuppressFBWarnings(value="NN_NAKED_NOTIFY", justification="Will have to be re-desigend; postponed.")
public void resume() {
synchronized (this) {
notify();
}
}
public void stop() {
stopped = true;
resume();
}
public long getId() {
return id;
}
boolean isStopped() {
return stopped;
}
private abstract static class DebugMapModel implements TemplateHashModelEx {
public int size() {
return keySet().size();
}
public TemplateCollectionModel keys() {
return new SimpleCollection(keySet());
}
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);
}
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(new String[]
{
Configurable.ARITHMETIC_ENGINE_KEY,
Configurable.BOOLEAN_FORMAT_KEY,
Configurable.CLASSIC_COMPATIBLE_KEY,
Configurable.LOCALE_KEY,
Configurable.NUMBER_FORMAT_KEY,
Configurable.OBJECT_WRAPPER_KEY,
Configurable.TEMPLATE_EXCEPTION_HANDLER_KEY
});
final Configurable configurable;
DebugConfigurableModel(Configurable configurable) {
this.configurable = configurable;
}
@Override
Collection keySet() {
return KEYS;
}
public TemplateModel get(String key) throws TemplateModelException {
String s = configurable.getSetting(key);
return s == null ? null : new SimpleScalar(s);
}
}
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) configurable).getSharedVariableNames();
}
public TemplateModel get(String key) {
return ((Configuration) configurable).getSharedVariable(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(new String[] {
"configuration",
"name",
}));
private final SimpleScalar name;
DebugTemplateModel(Template template) {
super(template);
this.name = new SimpleScalar(template.getName());
}
@Override
Collection keySet() {
return KEYS;
}
@Override
public TemplateModel get(String key) throws TemplateModelException {
if ("configuration".equals(key)) {
try {
return (TemplateModel) getCachedWrapperFor(((Template) configurable).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(new String[] {
"currentNamespace",
"dataModel",
"globalNamespace",
"knownVariables",
"mainNamespace",
"template",
}));
private TemplateModel knownVariables = new DebugMapModel()
{
@Override
Collection keySet() {
try {
return ((Environment) configurable).getKnownVariableNames();
} catch (TemplateModelException e) {
throw new UndeclaredThrowableException(e);
}
}
public TemplateModel get(String key) throws TemplateModelException {
return ((Environment) configurable).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) configurable).getCurrentNamespace();
}
if ("dataModel".equals(key)) {
return ((Environment) configurable).getDataModel();
}
if ("globalNamespace".equals(key)) {
return ((Environment) configurable).getGlobalNamespace();
}
if ("knownVariables".equals(key)) {
return knownVariables;
}
if ("mainNamespace".equals(key)) {
return ((Environment) configurable).getMainNamespace();
}
if ("template".equals(key)) {
try {
return (TemplateModel) getCachedWrapperFor(((Environment) configurable).getTemplate());
} 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) {
}
}
}
}