CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-freemarker--freemarker

Apache FreeMarker is a template engine: a Java library to generate text output based on templates and changing data.

Pending
Overview
Eval results
Files

object-wrapping.mddocs/

Object Wrapping

Object wrapping is the process of converting Java objects into TemplateModel objects that can be used in FreeMarker templates. The ObjectWrapper interface and its implementations provide this functionality.

Core Object Wrapper Interface

interface ObjectWrapper {
  TemplateModel wrap(Object obj) throws TemplateModelException;
}

// Enhanced object wrapper with unwrapping capabilities
interface RichObjectWrapper extends ObjectWrapper {
  TemplateModel wrap(Object obj) throws TemplateModelException;
}

Default Object Wrapper

The DefaultObjectWrapper is the recommended object wrapper for most use cases. It extends BeansWrapper and adds support for DOM nodes.

class DefaultObjectWrapper extends BeansWrapper {
  // Constructors
  DefaultObjectWrapper(Version incompatibleImprovements);
  
  // Factory method for default instance
  static DefaultObjectWrapper getDefaultInstance();
  
  // Wrapping methods
  TemplateModel wrap(Object obj) throws TemplateModelException;
  
  // Configuration inherited from BeansWrapper
  void setExposeFields(boolean exposeFields);
  boolean isExposeFields();
  void setExposureLevel(int exposureLevel);
  int getExposureLevel();
}

Usage Example

// Create with specific version
DefaultObjectWrapper wrapper = new DefaultObjectWrapper(Configuration.VERSION_2_3_34);

// Use default instance (shared, thread-safe)
DefaultObjectWrapper wrapper = DefaultObjectWrapper.getDefaultInstance();

// Configure in Configuration
Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
cfg.setObjectWrapper(wrapper);

Default Object Wrapper Builder

For advanced configuration, use the builder pattern:

class DefaultObjectWrapperBuilder extends DefaultObjectWrapperConfiguration {
  // Constructor
  DefaultObjectWrapperBuilder(Version incompatibleImprovements);
  
  // Build method
  DefaultObjectWrapper build();
  
  // Configuration methods
  DefaultObjectWrapperBuilder setExposeFields(boolean exposeFields);
  DefaultObjectWrapperBuilder setExposureLevel(int exposureLevel);
  DefaultObjectWrapperBuilder setForceLegacyNonListCollections(boolean force);
  DefaultObjectWrapperBuilder setDefaultDateType(int defaultDateType);
  DefaultObjectWrapperBuilder setOuterIdentity(ObjectWrapperAndUnwrapper outerIdentity);
  DefaultObjectWrapperBuilder setStrict(boolean strict);
  DefaultObjectWrapperBuilder setUseModelCache(boolean useModelCache);
  DefaultObjectWrapperBuilder setWrapAsHashModel(boolean wrapAsHashModel);
}

Builder Usage Example

DefaultObjectWrapper wrapper = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_34)
    .setExposeFields(true)
    .setExposureLevel(BeansWrapper.EXPOSE_PROPERTIES_ONLY)
    .setForceLegacyNonListCollections(false)
    .setDefaultDateType(TemplateDateModel.DATETIME)
    .build();

Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
cfg.setObjectWrapper(wrapper);

Beans Wrapper

The BeansWrapper is the foundation for Java bean wrapping, providing access to properties, methods, and fields.

class BeansWrapper implements RichObjectWrapper, WriteProtectable {
  // Constructors
  BeansWrapper();
  BeansWrapper(Version incompatibleImprovements);
  
  // Core wrapping methods
  TemplateModel wrap(Object obj) throws TemplateModelException;
  Object unwrap(TemplateModel model) throws TemplateModelException;
  Object unwrap(TemplateModel model, Class targetClass) throws TemplateModelException;
  
  // Static and enum model access
  TemplateHashModel getStaticModels();
  TemplateHashModel getEnumModels();
  
  // Field exposure configuration
  void setExposeFields(boolean exposeFields);
  boolean isExposeFields();
  
  // Exposure level control
  void setExposureLevel(int exposureLevel);
  int getExposureLevel();
  
  // Method appearance customization
  void setMethodAppearanceFineTuner(MethodAppearanceFineTuner tuner);
  MethodAppearanceFineTuner getMethodAppearanceFineTuner();
  
  // Security and access control
  void setSecurityManager(SecurityManager securityManager);
  SecurityManager getSecurityManager();
  
  // Null model handling
  void setNullModel(TemplateModel nullModel);
  TemplateModel getNullModel();
  
  // Date type configuration
  void setDefaultDateType(int defaultDateType);
  int getDefaultDateType();
  
  // Outer identity (for unwrapping)
  void setOuterIdentity(ObjectWrapperAndUnwrapper outerIdentity);
  ObjectWrapperAndUnwrapper getOuterIdentity();
  
  // Strict bean access
  void setStrict(boolean strict);
  boolean isStrict();
  
  // Model caching
  void setUseModelCache(boolean useCache);
  boolean getUseModelCache();
  void clearModelCache();
  
  // Constants for exposure levels
  static final int EXPOSE_ALL = 0;
  static final int EXPOSE_PROPERTIES_ONLY = 1;
  static final int EXPOSE_NOTHING = 2;
}

BeansWrapper Usage Examples

// Basic beans wrapper setup
BeansWrapper wrapper = new BeansWrapper(Configuration.VERSION_2_3_34);
wrapper.setExposeFields(true);
wrapper.setExposureLevel(BeansWrapper.EXPOSE_PROPERTIES_ONLY);

// Access static methods and fields
TemplateHashModel staticModels = wrapper.getStaticModels();
TemplateModel stringClass = staticModels.get("java.lang.String");
// In template: ${statics["java.lang.String"].valueOf(123)}

// Access enum constants
TemplateHashModel enumModels = wrapper.getEnumModels();
TemplateModel dayOfWeek = enumModels.get("java.time.DayOfWeek");
// In template: ${enums["java.time.DayOfWeek"].MONDAY}

// Custom method appearance
wrapper.setMethodAppearanceFineTuner(new MethodAppearanceFineTuner() {
    public void process(MethodAppearanceDecision decision, Method method, Class clazz) {
        if (method.getName().startsWith("internal")) {
            decision.setExposeMethodAs(null); // Hide internal methods
        }
    }
});

Static Models Access

class StaticModels implements TemplateHashModel {
  StaticModels(BeansWrapper wrapper);
  TemplateModel get(String key) throws TemplateModelException;
  boolean isEmpty() throws TemplateModelException;
}

// Individual static model for a class
class StaticModel implements TemplateHashModel, TemplateScalarModel {
  StaticModel(Class clazz, BeansWrapper wrapper);
  TemplateModel get(String key) throws TemplateModelException;
  boolean isEmpty() throws TemplateModelException;
  String getAsString() throws TemplateModelException;
}

Static Models Usage

// Setup static models access
BeansWrapper wrapper = new BeansWrapper(Configuration.VERSION_2_3_34);
TemplateHashModel staticModels = wrapper.getStaticModels();

Map<String, Object> dataModel = new HashMap<>();
dataModel.put("statics", staticModels);

// In template, access static methods:
// ${statics["java.lang.Math"].max(10, 20)}
// ${statics["java.util.Collections"].emptyList()}
// ${statics["java.lang.System"].currentTimeMillis()}

Enum Models Access

class EnumModels implements TemplateHashModel {
  EnumModels(BeansWrapper wrapper);
  TemplateModel get(String key) throws TemplateModelException;
  boolean isEmpty() throws TemplateModelException;
}

// Individual enum model
class EnumModel extends StaticModel {
  EnumModel(Class enumClass, BeansWrapper wrapper);
}

Enum Models Usage

// Setup enum models access
BeansWrapper wrapper = new BeansWrapper(Configuration.VERSION_2_3_34);
TemplateHashModel enumModels = wrapper.getEnumModels();

Map<String, Object> dataModel = new HashMap<>();
dataModel.put("enums", enumModels);

// In template, access enum constants:
// ${enums["java.time.DayOfWeek"].MONDAY}
// ${enums["java.util.concurrent.TimeUnit"].SECONDS}

Simple Object Wrapper

For simpler use cases that don't require bean introspection:

class SimpleObjectWrapper extends DefaultObjectWrapper {
  SimpleObjectWrapper();
  SimpleObjectWrapper(boolean simpleMapWrapper);
  
  TemplateModel wrap(Object obj) throws TemplateModelException;
}

SimpleObjectWrapper Usage

// Simple wrapper that uses only Simple* model classes
SimpleObjectWrapper wrapper = new SimpleObjectWrapper();
Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
cfg.setObjectWrapper(wrapper);

// This wrapper will convert:
// - Strings to SimpleScalar
// - Numbers to SimpleNumber  
// - Booleans to TemplateBooleanModel.TRUE/FALSE
// - Collections to SimpleSequence
// - Maps to SimpleHash
// - Other objects using toString() to SimpleScalar

Method Appearance Customization

interface MethodAppearanceFineTuner {
  void process(MethodAppearanceDecision decision, Method method, Class clazz);
}

class MethodAppearanceDecision {
  void setExposeMethodAs(String name);
  void setMethodShadowsProperty(boolean shadows);
  void setReplaceExistingProperty(boolean replace);
  String getExposeMethodAs();
  boolean getMethodShadowsProperty();
  boolean getReplaceExistingProperty();
}

Method Appearance Customization Example

BeansWrapper wrapper = new BeansWrapper(Configuration.VERSION_2_3_34);

wrapper.setMethodAppearanceFineTuner(new MethodAppearanceFineTuner() {
    public void process(MethodAppearanceDecision decision, Method method, Class clazz) {
        String methodName = method.getName();
        
        // Hide methods starting with underscore
        if (methodName.startsWith("_")) {
            decision.setExposeMethodAs(null);
        }
        
        // Rename getXXX methods to just XXX for cleaner template access
        if (methodName.startsWith("get") && methodName.length() > 3) {
            String propertyName = methodName.substring(3);
            propertyName = Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1);
            decision.setExposeMethodAs(propertyName);
            decision.setMethodShadowsProperty(false);
        }
        
        // Hide deprecated methods
        if (method.isAnnotationPresent(Deprecated.class)) {
            decision.setExposeMethodAs(null);
        }
    }
});

Bean Model Classes

BeanModel

class BeanModel implements TemplateHashModel, AdapterTemplateModel, WrapperTemplateModel {
  BeanModel(Object object, BeansWrapper wrapper);
  TemplateModel get(String key) throws TemplateModelException;
  boolean isEmpty() throws TemplateModelException;
  Object getAdaptedObject(Class hint);
  Object getWrappedObject();
  BeansWrapper getBeansWrapper();
}

StringModel

class StringModel extends BeanModel implements TemplateScalarModel {
  StringModel(Object object, BeansWrapper wrapper);
  String getAsString() throws TemplateModelException;
}

NumberModel

class NumberModel extends BeanModel implements TemplateNumberModel {
  NumberModel(Number number, BeansWrapper wrapper);
  Number getAsNumber() throws TemplateModelException;
}

DateModel

class DateModel extends BeanModel implements TemplateDateModel, TemplateHashModel {
  DateModel(Date date, BeansWrapper wrapper);
  DateModel(Date date, BeansWrapper wrapper, int type);
  Date getAsDate() throws TemplateModelException;
  int getDateType();
}

Wrapping Behavior

Automatic Wrapping Rules

The DefaultObjectWrapper automatically wraps objects according to these rules:

  1. nullTemplateModel.NOTHING
  2. TemplateModel → returned as-is
  3. StringStringModel (extends both BeanModel and TemplateScalarModel)
  4. NumberNumberModel (extends both BeanModel and TemplateNumberModel)
  5. DateDateModel (extends both BeanModel and TemplateDateModel)
  6. BooleanTemplateBooleanModel.TRUE or TemplateBooleanModel.FALSE
  7. MapDefaultMapAdapter (implements TemplateHashModelEx2)
  8. ListDefaultListAdapter (implements TemplateSequenceModel)
  9. ArrayDefaultArrayAdapter (implements TemplateSequenceModel)
  10. CollectionDefaultCollectionAdapter (implements TemplateCollectionModel)
  11. IteratorDefaultIteratorAdapter (implements TemplateCollectionModel)
  12. EnumerationDefaultEnumerationAdapter (implements TemplateCollectionModel)
  13. Node (DOM) → NodeModel (if DOM support enabled)
  14. Other objectsBeanModel (provides access to properties and methods)

Custom Wrapping

You can create custom object wrappers for specialized behavior:

public class CustomObjectWrapper extends DefaultObjectWrapper {
    public CustomObjectWrapper(Version incompatibleImprovements) {
        super(incompatibleImprovements);
    }
    
    @Override
    public TemplateModel wrap(Object obj) throws TemplateModelException {
        // Custom wrapping for specific types
        if (obj instanceof MyCustomClass) {
            return new MyCustomTemplateModel((MyCustomClass) obj);
        }
        
        // Fall back to default wrapping
        return super.wrap(obj);
    }
}

Performance Considerations

Model Caching

BeansWrapper caches introspection results and model instances for better performance:

// Enable model caching (default: true)
wrapper.setUseModelCache(true);

// Clear cache when needed
wrapper.clearModelCache();

Thread Safety

  • DefaultObjectWrapper.getDefaultInstance() returns a shared, thread-safe instance
  • Custom wrapper instances should be shared across templates for better performance
  • BeansWrapper instances are thread-safe after configuration is complete

Exposure Level Impact

Different exposure levels affect performance:

// Fastest - only exposes properties
wrapper.setExposureLevel(BeansWrapper.EXPOSE_PROPERTIES_ONLY);

// Slower - exposes properties, methods, and fields
wrapper.setExposureLevel(BeansWrapper.EXPOSE_ALL);

// Fastest but most restrictive - no bean introspection
wrapper.setExposureLevel(BeansWrapper.EXPOSE_NOTHING);

Install with Tessl CLI

npx tessl i tessl/maven-org-freemarker--freemarker

docs

caching-loading.md

core-processing.md

exception-handling.md

extensions.md

index.md

object-wrapping.md

output-formats.md

template-models.md

tile.json