blob: cac4c53132aa2172a6680e76f8b9b21fd8c3aa52 [file] [log] [blame]
package org.apache.velocity.runtime;
/*
* 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.
*/
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.velocity.runtime.directive.VelocimacroProxy;
import org.apache.velocity.runtime.parser.node.Node;
import org.apache.velocity.runtime.parser.node.SimpleNode;
import org.apache.velocity.util.MapFactory;
/**
* Manages VMs in namespaces. Currently, two namespace modes are
* supported:
*
* <ul>
* <li>flat - all allowable VMs are in the global namespace</li>
* <li>local - inline VMs are added to it's own template namespace</li>
* </ul>
*
* Thanks to <a href="mailto:JFernandez@viquity.com">Jose Alberto Fernandez</a>
* for some ideas incorporated here.
*
* @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
* @author <a href="mailto:JFernandez@viquity.com">Jose Alberto Fernandez</a>
* @version $Id$
*/
public class VelocimacroManager
{
private static String GLOBAL_NAMESPACE = "";
private boolean registerFromLib = false;
/** Hash of namespace hashes. */
private final Map namespaceHash = MapFactory.create(17, 0.5f, 20, false);
/** reference to global namespace hash */
private final Map globalNamespace;
/** set of names of library tempates/namespaces */
private final Set libraries = Collections.synchronizedSet(new HashSet());
/*
* big switch for namespaces. If true, then properties control
* usage. If false, no.
*/
private boolean namespacesOn = true;
private boolean inlineLocalMode = false;
private boolean inlineReplacesGlobal = false;
/**
* Adds the global namespace to the hash.
*/
VelocimacroManager(RuntimeServices rsvc)
{
/*
* add the global namespace to the namespace hash. We always have that.
*/
globalNamespace = addNamespace(GLOBAL_NAMESPACE);
}
/**
* Adds a VM definition to the cache.
*
* Called by VelocimacroFactory.addVelociMacro (after parsing and discovery in Macro directive)
*
* @param vmName Name of the new VelociMacro.
* @param macroBody String representation of the macro body.
* @param argArray Array of macro parameters, first parameter is the macro name.
* @param namespace The namespace/template from which this macro has been loaded.
* @return Whether everything went okay.
*/
public boolean addVM(final String vmName, final Node macroBody, final String argArray[],
final String namespace, boolean canReplaceGlobalMacro)
{
if (macroBody == null)
{
// happens only if someone uses this class without the Macro directive
// and provides a null value as an argument
throw new RuntimeException("Null AST for "+vmName+" in "+namespace);
}
MacroEntry me = new MacroEntry(vmName, macroBody, argArray, namespace);
me.setFromLibrary(registerFromLib);
/*
* the client (VMFactory) will signal to us via
* registerFromLib that we are in startup mode registering
* new VMs from libraries. Therefore, we want to
* addto the library map for subsequent auto reloads
*/
boolean isLib = true;
MacroEntry exist = (MacroEntry) globalNamespace.get(vmName);
if (registerFromLib)
{
libraries.add(namespace);
}
else
{
/*
* now, we first want to check to see if this namespace (template)
* is actually a library - if so, we need to use the global namespace
* we don't have to do this when registering, as namespaces should
* be shut off. If not, the default value is true, so we still go
* global
*/
isLib = libraries.contains(namespace);
}
if ( !isLib && usingNamespaces(namespace) )
{
/*
* first, do we have a namespace hash already for this namespace?
* if not, add it to the namespaces, and add the VM
*/
Map local = getNamespace(namespace, true);
local.put(vmName, me);
return true;
}
else
{
/*
* otherwise, add to global template. First, check if we
* already have it to preserve some of the autoload information
*/
if (exist != null)
{
me.setFromLibrary(exist.getFromLibrary());
}
/*
* now add it
*/
globalNamespace.put(vmName, me);
return true;
}
}
/**
* Gets a VelocimacroProxy object by the name / source template duple.
*
* @param vmName Name of the VelocityMacro to look up.
* @param namespace Namespace in which to look up the macro.
* @return A proxy representing the Macro.
*/
public VelocimacroProxy get(final String vmName, final String namespace)
{
return(get(vmName, namespace, null));
}
/**
* Gets a VelocimacroProxy object by the name / source template duple.
*
* @param vmName Name of the VelocityMacro to look up.
* @param namespace Namespace in which to look up the macro.
* @param renderingTemplate Name of the template we are currently rendering.
* @return A proxy representing the Macro.
* @since 1.6
*/
public VelocimacroProxy get(final String vmName, final String namespace, final String renderingTemplate)
{
if( inlineReplacesGlobal && renderingTemplate != null )
{
/*
* if VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL is true (local macros can
* override global macros) and we know which template we are rendering at the
* moment, check if local namespace contains a macro we are looking for
* if so, return it instead of the global one
*/
Map local = getNamespace(renderingTemplate, false);
if (local != null)
{
MacroEntry me = (MacroEntry) local.get(vmName);
if (me != null)
{
return me.getProxy(namespace);
}
}
}
if (usingNamespaces(namespace))
{
Map local = getNamespace(namespace, false);
/*
* if we have macros defined for this template
*/
if (local != null)
{
MacroEntry me = (MacroEntry) local.get(vmName);
if (me != null)
{
return me.getProxy(namespace);
}
}
}
/*
* if we didn't return from there, we need to simply see
* if it's in the global namespace
*/
MacroEntry me = (MacroEntry) globalNamespace.get(vmName);
if (me != null)
{
return me.getProxy(namespace);
}
return null;
}
/**
* Removes the VMs and the namespace from the manager.
* Used when a template is reloaded to avoid
* losing memory.
*
* @param namespace namespace to dump
* @return boolean representing success
*/
public boolean dumpNamespace(final String namespace)
{
synchronized(this)
{
if (usingNamespaces(namespace))
{
Map h = (Map) namespaceHash.remove(namespace);
if (h == null)
{
return false;
}
h.clear();
return true;
}
return false;
}
}
/**
* public switch to let external user of manager to control namespace
* usage indep of properties. That way, for example, at startup the
* library files are loaded into global namespace
*
* @param namespaceOn True if namespaces should be used.
*/
public void setNamespaceUsage(final boolean namespaceOn)
{
this.namespacesOn = namespaceOn;
}
/**
* Should macros registered from Libraries be marked special?
* @param registerFromLib True if macros from Libs should be marked.
*/
public void setRegisterFromLib(final boolean registerFromLib)
{
this.registerFromLib = registerFromLib;
}
/**
* Should macros from the same template be inlined?
*
* @param inlineLocalMode True if macros should be inlined on the same template.
*/
public void setTemplateLocalInlineVM(final boolean inlineLocalMode)
{
this.inlineLocalMode = inlineLocalMode;
}
/**
* returns the hash for the specified namespace, and if it doesn't exist
* will create a new one and add it to the namespaces
*
* @param namespace name of the namespace :)
* @param addIfNew flag to add a new namespace if it doesn't exist
* @return namespace Map of VMs or null if doesn't exist
*/
private Map getNamespace(final String namespace, final boolean addIfNew)
{
Map h = (Map) namespaceHash.get(namespace);
if (h == null && addIfNew)
{
h = addNamespace(namespace);
}
return h;
}
/**
* adds a namespace to the namespaces
*
* @param namespace name of namespace to add
* @return Hash added to namespaces, ready for use
*/
private Map addNamespace(final String namespace)
{
Map h = MapFactory.create(17, 0.5f, 20, false);
Object oh;
if ((oh = namespaceHash.put(namespace, h)) != null)
{
/*
* There was already an entry on the table, restore it!
* This condition should never occur, given the code
* and the fact that this method is private.
* But just in case, this way of testing for it is much
* more efficient than testing before hand using get().
*/
namespaceHash.put(namespace, oh);
/*
* Should't we be returning the old entry (oh)?
* The previous code was just returning null in this case.
*/
return null;
}
return h;
}
/**
* determines if currently using namespaces.
*
* @param namespace currently ignored
* @return true if using namespaces, false if not
*/
private boolean usingNamespaces(final String namespace)
{
/*
* if the big switch turns of namespaces, then ignore the rules
*/
if (!namespacesOn)
{
return false;
}
/*
* currently, we only support the local template namespace idea
*/
if (inlineLocalMode)
{
return true;
}
return false;
}
/**
* Return the library name for a given macro.
* @param vmName Name of the Macro to look up.
* @param namespace Namespace to look the macro up.
* @return The name of the library which registered this macro in a namespace.
*/
public String getLibraryName(final String vmName, final String namespace)
{
if (usingNamespaces(namespace))
{
Map local = getNamespace(namespace, false);
/*
* if we have this macro defined in this namespace, then
* it is masking the global, library-based one, so
* just return null
*/
if ( local != null)
{
MacroEntry me = (MacroEntry) local.get(vmName);
if (me != null)
{
return null;
}
}
}
/*
* if we didn't return from there, we need to simply see
* if it's in the global namespace
*/
MacroEntry me = (MacroEntry) globalNamespace.get(vmName);
if (me != null)
{
return me.getSourceTemplate();
}
return null;
}
/**
* @since 1.6
*/
public void setInlineReplacesGlobal(boolean is)
{
inlineReplacesGlobal = is;
}
/**
* wrapper class for holding VM information
*/
private static class MacroEntry
{
private final String vmName;
private final String[] argArray;
private final String sourceTemplate;
private SimpleNode nodeTree = null;
private boolean fromLibrary = false;
private VelocimacroProxy vp;
private MacroEntry(final String vmName, final Node macro,
final String argArray[], final String sourceTemplate)
{
this.vmName = vmName;
this.argArray = argArray;
this.nodeTree = (SimpleNode)macro;
this.sourceTemplate = sourceTemplate;
vp = new VelocimacroProxy();
vp.setName(this.vmName);
vp.setArgArray(this.argArray);
vp.setNodeTree(this.nodeTree);
}
/**
* Has the macro been registered from a library.
* @param fromLibrary True if the macro was registered from a Library.
*/
public void setFromLibrary(final boolean fromLibrary)
{
this.fromLibrary = fromLibrary;
}
/**
* Returns true if the macro was registered from a library.
* @return True if the macro was registered from a library.
*/
public boolean getFromLibrary()
{
return fromLibrary;
}
/**
* Returns the node tree for this macro.
* @return The node tree for this macro.
*/
public SimpleNode getNodeTree()
{
return nodeTree;
}
/**
* Returns the source template name for this macro.
* @return The source template name for this macro.
*/
public String getSourceTemplate()
{
return sourceTemplate;
}
VelocimacroProxy getProxy(final String namespace)
{
/*
* FIXME: namespace data is omitted, this probably
* breaks some error reporting?
*/
return vp;
}
}
}