/*
 * 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.template.utility;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

import freemarker.core.Environment;
import freemarker.core.Macro;
import freemarker.core.TemplateMarkupOutputModel;
import freemarker.ext.beans.BeanModel;
import freemarker.ext.beans.BooleanModel;
import freemarker.ext.beans.CollectionModel;
import freemarker.ext.beans.DateModel;
import freemarker.ext.beans.EnumerationModel;
import freemarker.ext.beans.IteratorModel;
import freemarker.ext.beans.MapModel;
import freemarker.ext.beans.NumberModel;
import freemarker.ext.beans.OverloadedMethodsModel;
import freemarker.ext.beans.SimpleMethodModel;
import freemarker.ext.beans.StringModel;
import freemarker.ext.util.WrapperTemplateModel;
import freemarker.template.AdapterTemplateModel;
import freemarker.template.TemplateBooleanModel;
import freemarker.template.TemplateCollectionModel;
import freemarker.template.TemplateCollectionModelEx;
import freemarker.template.TemplateDateModel;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateHashModelEx;
import freemarker.template.TemplateMethodModel;
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelIterator;
import freemarker.template.TemplateNodeModel;
import freemarker.template.TemplateNodeModelEx;
import freemarker.template.TemplateNumberModel;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
import freemarker.template.TemplateTransformModel;

/**
 */
public class ClassUtil {
    private ClassUtil() {
    }
    
    
Similar to Class.forName(String), but attempts to load through the thread context class loader. Only if thread context class loader is inaccessible, or it can't find the class will it attempt to fall back to the class loader that loads the FreeMarker classes.
/** * Similar to {@link Class#forName(java.lang.String)}, but attempts to load * through the thread context class loader. Only if thread context class * loader is inaccessible, or it can't find the class will it attempt to * fall back to the class loader that loads the FreeMarker classes. */
public static Class forName(String className) throws ClassNotFoundException { try { ClassLoader ctcl = Thread.currentThread().getContextClassLoader(); if (ctcl != null) { // not null: we don't want to fall back to the bootstrap class loader return Class.forName(className, true, ctcl); } } catch (ClassNotFoundException e) { ;// Intentionally ignored } catch (SecurityException e) { ;// Intentionally ignored } // Fall back to the defining class loader of the FreeMarker classes return Class.forName(className); }
Since:2.3.20
/** * Same as {@link #getShortClassName(Class, boolean) getShortClassName(pClass, false)}. * * @since 2.3.20 */
public static String getShortClassName(Class pClass) { return getShortClassName(pClass, false); }
Returns a class name without "java.lang." and "java.util." prefix, also shows array types in a format like int[]; useful for printing class names in error messages.
Params:
  • pClass – can be null, in which case the method returns null.
  • shortenFreeMarkerClasses – if true, it will also shorten FreeMarker class names. The exact rules aren't specified and might change over time, but right now, freemarker.ext.beans.NumberModel for example becomes to f.e.b.NumberModel.
Since:2.3.20
/** * Returns a class name without "java.lang." and "java.util." prefix, also shows array types in a format like * {@code int[]}; useful for printing class names in error messages. * * @param pClass can be {@code null}, in which case the method returns {@code null}. * @param shortenFreeMarkerClasses if {@code true}, it will also shorten FreeMarker class names. The exact rules * aren't specified and might change over time, but right now, {@code freemarker.ext.beans.NumberModel} for * example becomes to {@code f.e.b.NumberModel}. * * @since 2.3.20 */
public static String getShortClassName(Class pClass, boolean shortenFreeMarkerClasses) { if (pClass == null) { return null; } else if (pClass.isArray()) { return getShortClassName(pClass.getComponentType()) + "[]"; } else { String cn = pClass.getName(); if (cn.startsWith("java.lang.") || cn.startsWith("java.util.")) { return cn.substring(10); } else { if (shortenFreeMarkerClasses) { if (cn.startsWith("freemarker.template.")) { return "f.t" + cn.substring(19); } else if (cn.startsWith("freemarker.ext.beans.")) { return "f.e.b" + cn.substring(20); } else if (cn.startsWith("freemarker.core.")) { return "f.c" + cn.substring(15); } else if (cn.startsWith("freemarker.ext.")) { return "f.e" + cn.substring(14); } else if (cn.startsWith("freemarker.")) { return "f" + cn.substring(10); } // Falls through } return cn; } } }
Since:2.3.20
/** * Same as {@link #getShortClassNameOfObject(Object, boolean) getShortClassNameOfObject(pClass, false)}. * * @since 2.3.20 */
public static String getShortClassNameOfObject(Object obj) { return getShortClassNameOfObject(obj, false); }
getShortClassName(Class, boolean) called with object.getClass(), but returns the fictional class name Null for a null value.
Since:2.3.20
/** * {@link #getShortClassName(Class, boolean)} called with {@code object.getClass()}, but returns the fictional * class name {@code Null} for a {@code null} value. * * @since 2.3.20 */
public static String getShortClassNameOfObject(Object obj, boolean shortenFreeMarkerClasses) { if (obj == null) { return "Null"; } else { return ClassUtil.getShortClassName(obj.getClass(), shortenFreeMarkerClasses); } }
Returns the TemplateModel interface that is the most characteristic of the object, or null.
/** * Returns the {@link TemplateModel} interface that is the most characteristic of the object, or {@code null}. */
private static Class getPrimaryTemplateModelInterface(TemplateModel tm) { if (tm instanceof BeanModel) { if (tm instanceof CollectionModel) { return TemplateSequenceModel.class; } else if (tm instanceof IteratorModel || tm instanceof EnumerationModel) { return TemplateCollectionModel.class; } else if (tm instanceof MapModel) { return TemplateHashModelEx.class; } else if (tm instanceof NumberModel) { return TemplateNumberModel.class; } else if (tm instanceof BooleanModel) { return TemplateBooleanModel.class; } else if (tm instanceof DateModel) { return TemplateDateModel.class; } else if (tm instanceof StringModel) { Object wrapped = ((BeanModel) tm).getWrappedObject(); return wrapped instanceof String ? TemplateScalarModel.class : (tm instanceof TemplateHashModelEx ? TemplateHashModelEx.class : null); } else { return null; } } else if (tm instanceof SimpleMethodModel || tm instanceof OverloadedMethodsModel) { return TemplateMethodModelEx.class; } else { return null; } } private static void appendTemplateModelTypeName(StringBuilder sb, Set typeNamesAppended, Class cl) { int initalLength = sb.length(); if (TemplateNodeModelEx.class.isAssignableFrom(cl)) { appendTypeName(sb, typeNamesAppended, "extended node"); } else if (TemplateNodeModel.class.isAssignableFrom(cl)) { appendTypeName(sb, typeNamesAppended, "node"); } if (TemplateDirectiveModel.class.isAssignableFrom(cl)) { appendTypeName(sb, typeNamesAppended, "directive"); } else if (TemplateTransformModel.class.isAssignableFrom(cl)) { appendTypeName(sb, typeNamesAppended, "transform"); } if (TemplateSequenceModel.class.isAssignableFrom(cl)) { appendTypeName(sb, typeNamesAppended, "sequence"); } else if (TemplateCollectionModel.class.isAssignableFrom(cl)) { appendTypeName(sb, typeNamesAppended, TemplateCollectionModelEx.class.isAssignableFrom(cl) ? "extended_collection" : "collection"); } else if (TemplateModelIterator.class.isAssignableFrom(cl)) { appendTypeName(sb, typeNamesAppended, "iterator"); } if (TemplateMethodModel.class.isAssignableFrom(cl)) { appendTypeName(sb, typeNamesAppended, "method"); } if (Environment.Namespace.class.isAssignableFrom(cl)) { appendTypeName(sb, typeNamesAppended, "namespace"); } else if (TemplateHashModelEx.class.isAssignableFrom(cl)) { appendTypeName(sb, typeNamesAppended, "extended_hash"); } else if (TemplateHashModel.class.isAssignableFrom(cl)) { appendTypeName(sb, typeNamesAppended, "hash"); } if (TemplateNumberModel.class.isAssignableFrom(cl)) { appendTypeName(sb, typeNamesAppended, "number"); } if (TemplateDateModel.class.isAssignableFrom(cl)) { appendTypeName(sb, typeNamesAppended, "date_or_time_or_datetime"); } if (TemplateBooleanModel.class.isAssignableFrom(cl)) { appendTypeName(sb, typeNamesAppended, "boolean"); } if (TemplateScalarModel.class.isAssignableFrom(cl)) { appendTypeName(sb, typeNamesAppended, "string"); } if (TemplateMarkupOutputModel.class.isAssignableFrom(cl)) { appendTypeName(sb, typeNamesAppended, "markup_output"); } if (sb.length() == initalLength) { appendTypeName(sb, typeNamesAppended, "misc_template_model"); } } private static Class getUnwrappedClass(TemplateModel tm) { Object unwrapped; try { if (tm instanceof WrapperTemplateModel) { unwrapped = ((WrapperTemplateModel) tm).getWrappedObject(); } else if (tm instanceof AdapterTemplateModel) { unwrapped = ((AdapterTemplateModel) tm).getAdaptedObject(Object.class); } else { unwrapped = null; } } catch (Throwable e) { unwrapped = null; } return unwrapped != null ? unwrapped.getClass() : null; } private static void appendTypeName(StringBuilder sb, Set typeNamesAppended, String name) { if (!typeNamesAppended.contains(name)) { if (sb.length() != 0) sb.append("+"); sb.append(name); typeNamesAppended.add(name); } }
Returns the type description of a value with FTL terms (not plain class name), as it should be used in type-related error messages and for debugging purposes. The exact format is not specified and might change over time, but currently it's something like "string (wrapper: f.t.SimpleScalar)" or "sequence+hash+string (ArrayList wrapped into f.e.b.CollectionModel)".
Since:2.3.20
/** * Returns the type description of a value with FTL terms (not plain class name), as it should be used in * type-related error messages and for debugging purposes. The exact format is not specified and might change over * time, but currently it's something like {@code "string (wrapper: f.t.SimpleScalar)"} or * {@code "sequence+hash+string (ArrayList wrapped into f.e.b.CollectionModel)"}. * * @since 2.3.20 */
public static String getFTLTypeDescription(TemplateModel tm) { if (tm == null) { return "Null"; } else { Set typeNamesAppended = new HashSet(); StringBuilder sb = new StringBuilder(); Class primaryInterface = getPrimaryTemplateModelInterface(tm); if (primaryInterface != null) { appendTemplateModelTypeName(sb, typeNamesAppended, primaryInterface); } if (tm instanceof Macro) { appendTypeName(sb, typeNamesAppended, ((Macro) tm).isFunction() ? "function" : "macro"); } appendTemplateModelTypeName(sb, typeNamesAppended, tm.getClass()); String javaClassName; Class unwrappedClass = getUnwrappedClass(tm); if (unwrappedClass != null) { javaClassName = getShortClassName(unwrappedClass, true); } else { javaClassName = null; } sb.append(" ("); String modelClassName = getShortClassName(tm.getClass(), true); if (javaClassName == null) { sb.append("wrapper: "); sb.append(modelClassName); } else { sb.append(javaClassName); sb.append(" wrapped into "); sb.append(modelClassName); } sb.append(")"); return sb.toString(); } }
Gets the wrapper class for a primitive class, like Integer for int, also returns Void for void.
Params:
  • primitiveClass – A Class like int.type, boolean.type, etc. If it's not a primitive class, or it's null, then the parameter value is returned as is. Note that performance-wise the method assumes that it's a primitive class.
Since:2.3.21
/** * Gets the wrapper class for a primitive class, like {@link Integer} for {@code int}, also returns {@link Void} * for {@code void}. * * @param primitiveClass A {@link Class} like {@code int.type}, {@code boolean.type}, etc. If it's not a primitive * class, or it's {@code null}, then the parameter value is returned as is. Note that performance-wise the * method assumes that it's a primitive class. * * @since 2.3.21 */
public static Class primitiveClassToBoxingClass(Class primitiveClass) { // Tried to sort these with decreasing frequency in API-s: if (primitiveClass == int.class) return Integer.class; if (primitiveClass == boolean.class) return Boolean.class; if (primitiveClass == long.class) return Long.class; if (primitiveClass == double.class) return Double.class; if (primitiveClass == char.class) return Character.class; if (primitiveClass == float.class) return Float.class; if (primitiveClass == byte.class) return Byte.class; if (primitiveClass == short.class) return Short.class; if (primitiveClass == void.class) return Void.class; // not really a primitive, but we normalize it return primitiveClass; }
The exact reverse of primitiveClassToBoxingClass.
Since:2.3.21
/** * The exact reverse of {@link #primitiveClassToBoxingClass}. * * @since 2.3.21 */
public static Class boxingClassToPrimitiveClass(Class boxingClass) { // Tried to sort these with decreasing frequency in API-s: if (boxingClass == Integer.class) return int.class; if (boxingClass == Boolean.class) return boolean.class; if (boxingClass == Long.class) return long.class; if (boxingClass == Double.class) return double.class; if (boxingClass == Character.class) return char.class; if (boxingClass == Float.class) return float.class; if (boxingClass == Byte.class) return byte.class; if (boxingClass == Short.class) return short.class; if (boxingClass == Void.class) return void.class; // not really a primitive, but we normalize to it return boxingClass; }
Tells if a type is numerical; works both for primitive types and classes.
Params:
  • type – can't be null
Since:2.3.21
/** * Tells if a type is numerical; works both for primitive types and classes. * * @param type can't be {@code null} * * @since 2.3.21 */
public static boolean isNumerical(Class type) { return Number.class.isAssignableFrom(type) || type.isPrimitive() && type != Boolean.TYPE && type != Character.TYPE && type != Void.TYPE; }
Very similar to Class.getResourceAsStream(String), but throws IOException instead of returning null if optional is false, and attempts to work around "IllegalStateException: zip file closed" and similar sun.net.www.protocol.jar.JarURLConnection-related glitches. These are caused by bugs outside of FreeMarker. Note that in cases where the JAR resource becomes broken concurrently, similar errors can still occur later when the InputStream is read (loadProperties(Class<?>, String) works that around as well).
Throws:
Returns:If optional is false, it's never null, otherwise null indicates that the resource doesn't exist.
Since:2.3.27
/** * Very similar to {@link Class#getResourceAsStream(String)}, but throws {@link IOException} instead of returning * {@code null} if {@code optional} is {@code false}, and attempts to work around "IllegalStateException: zip file * closed" and similar {@code sun.net.www.protocol.jar.JarURLConnection}-related glitches. These are caused by bugs * outside of FreeMarker. Note that in cases where the JAR resource becomes broken concurrently, similar errors can * still occur later when the {@link InputStream} is read ({@link #loadProperties(Class, String)} works that * around as well). * * @return If {@code optional} is {@code false}, it's never {@code null}, otherwise {@code null} indicates that the * resource doesn't exist. * @throws IOException * If the resource wasn't found, or other {@link IOException} occurs. * * @since 2.3.27 */
public static InputStream getReasourceAsStream(Class<?> baseClass, String resource, boolean optional) throws IOException { InputStream ins; try { // This is how we did this earlier. May uses some JarURLConnection caches, which leads to the problems. ins = baseClass.getResourceAsStream(resource); } catch (Exception e) { // Workaround for "IllegalStateException: zip file closed", and other related exceptions. This happens due // to bugs outside of FreeMarker, but we try to work it around anyway. URL url = baseClass.getResource(resource); ins = url != null ? url.openStream() : null; } if (!optional) { checkInputStreamNotNull(ins, baseClass, resource); } return ins; }
Same as getReasourceAsStream(Class<?>, String, boolean), but uses a ClassLoader directly instead of a Class.
Since:2.3.27
/** * Same as {@link #getReasourceAsStream(Class, String, boolean)}, but uses a {@link ClassLoader} directly * instead of a {@link Class}. * * @since 2.3.27 */
public static InputStream getReasourceAsStream(ClassLoader classLoader, String resource, boolean optional) throws IOException { // See source commends in the other overload of this method. InputStream ins; try { ins = classLoader.getResourceAsStream(resource); } catch (Exception e) { URL url = classLoader.getResource(resource); ins = url != null ? url.openStream() : null; } if (ins == null && !optional) { throw new IOException("Class-loader resource not found (shown quoted): " + StringUtil.jQuote(resource) + ". The base ClassLoader was: " + classLoader); } return ins; }
Loads a class loader resource into a Properties; tries to work around "zip file closed" and related sun.net.www.protocol.jar.JarURLConnection glitches.
Since:2.3.27
/** * Loads a class loader resource into a {@link Properties}; tries to work around "zip file closed" and related * {@code sun.net.www.protocol.jar.JarURLConnection} glitches. * * @since 2.3.27 */
public static Properties loadProperties(Class<?> baseClass, String resource) throws IOException { Properties props = new Properties(); InputStream ins = null; try { try { // This is how we did this earlier. May uses some JarURLConnection caches, which leads to the problems. ins = baseClass.getResourceAsStream(resource); } catch (Exception e) { throw new MaybeZipFileClosedException(); } checkInputStreamNotNull(ins, baseClass, resource); try { props.load(ins); } catch (Exception e) { throw new MaybeZipFileClosedException(); } finally { try { ins.close(); } catch (Exception e) { // Do nothing to suppress "ZipFile closed" and related exceptions. } ins = null; } } catch (MaybeZipFileClosedException e) { // Workaround for "zip file closed" exception, and other related exceptions. This happens due to bugs // outside of FreeMarker, but we try to work it around anyway. URL url = baseClass.getResource(resource); ins = url != null ? url.openStream() : null; checkInputStreamNotNull(ins, baseClass, resource); props.load(ins); } finally { if (ins != null) { try { ins.close(); } catch (Exception e) { // Do nothing to suppress "ZipFile closed" and related exceptions. } } } return props; } private static void checkInputStreamNotNull(InputStream ins, Class<?> baseClass, String resource) throws IOException { if (ins == null) { throw new IOException("Class-loader resource not found (shown quoted): " + StringUtil.jQuote(resource) + ". The base class was " + baseClass.getName() + "."); } }
Used internally to work around some JarURLConnection glitches
/** Used internally to work around some JarURLConnection glitches */
private static class MaybeZipFileClosedException extends Exception { // } }