org.apache.velocity.runtime.VelocimacroManager Maven / Gradle / Ivy
The newest version!
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 org.apache.velocity.Template;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.runtime.directive.Macro;
import org.apache.velocity.runtime.directive.VelocimacroProxy;
import org.apache.velocity.runtime.parser.node.Node;
import org.apache.velocity.runtime.parser.node.SimpleNode;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Manages VMs in namespaces. Currently, two namespace modes are
* supported:
*
*
* - flat - all allowable VMs are in the global namespace
* - local - inline VMs are added to it's own template namespace
*
*
* Thanks to Jose Alberto Fernandez
* for some ideas incorporated here.
*
* @author Geir Magnusson Jr.
* @author Jose Alberto Fernandez
* @version $Id$
*/
public class VelocimacroManager
{
private boolean registerFromLib = false;
/** reference to global namespace hash */
private final Map globalNamespace;
/** set of names of library templates/namespaces */
private final Map libraries = new ConcurrentHashMap<>(17, 0.5f, 20);
private RuntimeServices rsvc = null;
/*
* 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 = new ConcurrentHashMap<>(101, 0.5f, 20);
this.rsvc = rsvc;
}
/**
* 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 macroArgs Array of macro arguments, containing the
* #macro() arguments and default values. the 0th is the name.
* @param definingTemplate The template from which this macro has been loaded.
* @param canReplaceGlobalMacro whether this macro can replace a global macro
* @return Whether everything went okay.
*/
public boolean addVM(final String vmName, final Node macroBody, List macroArgs,
final Template definingTemplate, 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 VelocityException("Null AST for "+vmName+" in " + definingTemplate.getName());
}
MacroEntry me = new MacroEntry(vmName, macroBody, macroArgs, definingTemplate.getName(), rsvc);
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 = globalNamespace.get(vmName);
if (registerFromLib)
{
libraries.put(definingTemplate.getName(), definingTemplate);
}
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.containsKey(definingTemplate.getName());
}
if ( !isLib && usingNamespaces() )
{
definingTemplate.getMacros().put(vmName, me);
}
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 renderingTemplate Template we are currently rendering.
* @param template Source Template.
* @return A proxy representing the Macro.
*/
public VelocimacroProxy get(final String vmName, final Template renderingTemplate, final Template template)
{
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
*/
MacroEntry me = (MacroEntry)renderingTemplate.getMacros().get(vmName);
if( me != null )
{
return me.getProxy();
}
}
if( usingNamespaces() && template != null )
{
MacroEntry me = (MacroEntry)template.getMacros().get(vmName);
if( template.getMacros().size() > 0 && me != null )
{
return me.getProxy();
}
}
MacroEntry me = globalNamespace.get(vmName);
if (me != null)
{
return me.getProxy();
}
return null;
}
/**
* 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;
}
/**
* determines if currently using namespaces.
*
* @return true if using namespaces, false if not
*/
private boolean usingNamespaces()
{
/*
* if the big switch turns of namespaces, then ignore the rules
*/
if (!namespacesOn)
{
return false;
}
/*
* currently, we only support the local template namespace idea
*/
return inlineLocalMode;
}
/**
* Return the library name for a given macro.
* @param vmName Name of the Macro to look up.
* @param template Template
* @return The name of the library which registered this macro in a namespace.
*/
public String getLibraryName(final String vmName, Template template)
{
if (usingNamespaces())
{
/*
* if we have this macro defined in this namespace, then
* it is masking the global, library-based one, so
* just return null
*/
MacroEntry me = (MacroEntry)template.getMacros().get(vmName);
if( me != null )
return null;
}
MacroEntry me = globalNamespace.get(vmName);
if (me != null)
{
return me.getSourceTemplate();
}
return null;
}
/**
* @param is
* @since 1.6
*/
public void setInlineReplacesGlobal(boolean is)
{
inlineReplacesGlobal = is;
}
/**
* wrapper class for holding VM information
*/
private static class MacroEntry
{
private final String sourceTemplate;
private boolean fromLibrary = false;
private VelocimacroProxy vp;
private MacroEntry(final String vmName, final Node macro,
List macroArgs, final String sourceTemplate,
RuntimeServices rsvc)
{
this.sourceTemplate = sourceTemplate;
vp = new VelocimacroProxy();
vp.init(rsvc);
vp.setName(vmName);
vp.setMacroArgs(macroArgs);
vp.setNodeTree((SimpleNode)macro);
vp.setLocation(macro.getLine(), macro.getColumn(), macro.getTemplate());
}
/**
* 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;
}
public String getSourceTemplate()
{
return sourceTemplate;
}
VelocimacroProxy getProxy()
{
return vp;
}
}
}