/*
* 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.ext.beans;
import freemarker.template.
DefaultObjectWrapperBuilder;
import freemarker.template.
ObjectWrapper;
import freemarker.template.
TemplateDateModel;
import freemarker.template.
Version;
import freemarker.template.
_TemplateAPI;
/**
* Holds {@link BeansWrapper} configuration settings and defines their defaults.
* You will not use this abstract class directly, but concrete subclasses like {@link BeansWrapperBuilder} and
* {@link DefaultObjectWrapperBuilder}. Unless, you are developing a builder for a custom {@link BeansWrapper} subclass.
*
* <p>This class is designed so that its instances can be used as lookup keys in a singleton cache. This is also why
* this class defines the configuration setting defaults for {@link BeansWrapper}, instead of leaving that to
* {@link BeansWrapper} itself. (Because, the default values influence the lookup key, and the singleton needs to be
* looked up without creating a {@link BeansWrapper} instance.) However, because instances are mutable, you should
* deep-clone it with {@link #clone(boolean)} before using it as cache key.
*
* @since 2.3.21
*/
public abstract class
BeansWrapperConfiguration implements
Cloneable {
private final
Version incompatibleImprovements;
private
ClassIntrospectorBuilder classIntrospectorBuilder;
// Properties and their *defaults*:
private boolean
simpleMapWrapper = false;
private boolean
preferIndexedReadMethod;
private int
defaultDateType =
TemplateDateModel.
UNKNOWN;
private
ObjectWrapper outerIdentity = null;
private boolean
strict = false;
private boolean
useModelCache = false;
// Attention!
// - As this object is a cache key, non-normalized field values should be avoided.
// - Fields with default values must be set until the end of the constructor to ensure that when the lookup happens,
// there will be no unset fields.
// - If you add a new field, review all methods in this class
/**
* @param incompatibleImprovements
* See the corresponding parameter of {@link BeansWrapper#BeansWrapper(Version)}. Not {@code null}. Note
* that the version will be normalized to the lowest version where the same incompatible
* {@link BeansWrapper} improvements were already present, so for the returned instance
* {@link #getIncompatibleImprovements()} might returns a lower version than what you have specified
* here.
* @param isIncompImprsAlreadyNormalized
* Tells if the {@code incompatibleImprovements} parameter contains an <em>already normalized</em> value.
* This parameter meant to be {@code true} when the class that extends {@link BeansWrapper} needs to add
* additional breaking versions over those of {@link BeansWrapper}. Thus, if this parameter is
* {@code true}, the versions where {@link BeansWrapper} had breaking changes must be already factored
* into the {@code incompatibleImprovements} parameter value, as no more normalization will happen. (You
* can use {@link BeansWrapper#normalizeIncompatibleImprovementsVersion(Version)} to discover those.)
*
* @since 2.3.22
*/
protected
BeansWrapperConfiguration(
Version incompatibleImprovements, boolean
isIncompImprsAlreadyNormalized) {
_TemplateAPI.
checkVersionNotNullAndSupported(
incompatibleImprovements);
incompatibleImprovements =
isIncompImprsAlreadyNormalized
?
incompatibleImprovements
:
BeansWrapper.
normalizeIncompatibleImprovementsVersion(
incompatibleImprovements);
this.
incompatibleImprovements =
incompatibleImprovements;
preferIndexedReadMethod =
incompatibleImprovements.
intValue() <
_TemplateAPI.
VERSION_INT_2_3_27;
classIntrospectorBuilder = new
ClassIntrospectorBuilder(
incompatibleImprovements);
}
/**
* Same as {@link #BeansWrapperConfiguration(Version, boolean) BeansWrapperConfiguration(Version, false)}.
*/
protected
BeansWrapperConfiguration(
Version incompatibleImprovements) {
this(
incompatibleImprovements, false);
}
@
Override
public int
hashCode() {
final int
prime = 31;
int
result = 1;
result =
prime *
result +
incompatibleImprovements.
hashCode();
result =
prime *
result + (
simpleMapWrapper ? 1231 : 1237);
result =
prime *
result + (
preferIndexedReadMethod ? 1231 : 1237);
result =
prime *
result +
defaultDateType;
result =
prime *
result + (
outerIdentity != null ?
outerIdentity.
hashCode() : 0);
result =
prime *
result + (
strict ? 1231 : 1237);
result =
prime *
result + (
useModelCache ? 1231 : 1237);
result =
prime *
result +
classIntrospectorBuilder.
hashCode();
return
result;
}
/**
* Two {@link BeansWrapperConfiguration}-s are equal exactly if their classes are identical ({@code ==}), and their
* field values are equal.
*/
@
Override
public boolean
equals(
Object obj) {
if (this ==
obj) return true;
if (
obj == null) return false;
if (
getClass() !=
obj.
getClass()) return false;
BeansWrapperConfiguration other = (
BeansWrapperConfiguration)
obj;
if (!
incompatibleImprovements.
equals(
other.
incompatibleImprovements)) return false;
if (
simpleMapWrapper !=
other.
simpleMapWrapper) return false;
if (
preferIndexedReadMethod !=
other.
preferIndexedReadMethod) return false;
if (
defaultDateType !=
other.
defaultDateType) return false;
if (
outerIdentity !=
other.
outerIdentity) return false;
if (
strict !=
other.
strict) return false;
if (
useModelCache !=
other.
useModelCache) return false;
if (!
classIntrospectorBuilder.
equals(
other.
classIntrospectorBuilder)) return false;
return true;
}
protected
Object clone(boolean
deepCloneKey) {
try {
BeansWrapperConfiguration clone = (
BeansWrapperConfiguration) super.clone();
if (
deepCloneKey) {
clone.
classIntrospectorBuilder
= (
ClassIntrospectorBuilder)
classIntrospectorBuilder.
clone();
}
return
clone;
} catch (
CloneNotSupportedException e) {
throw new
RuntimeException("Failed to clone BeansWrapperConfiguration",
e);
}
}
public boolean
isSimpleMapWrapper() {
return
simpleMapWrapper;
}
/** See {@link BeansWrapper#setSimpleMapWrapper(boolean)}. */
public void
setSimpleMapWrapper(boolean
simpleMapWrapper) {
this.
simpleMapWrapper =
simpleMapWrapper;
}
/** @since 2.3.27 */
public boolean
getPreferIndexedReadMethod() {
return
preferIndexedReadMethod;
}
/** See {@link BeansWrapper#setPreferIndexedReadMethod(boolean)}. @since 2.3.27 */
public void
setPreferIndexedReadMethod(boolean
preferIndexedReadMethod) {
this.
preferIndexedReadMethod =
preferIndexedReadMethod;
}
public int
getDefaultDateType() {
return
defaultDateType;
}
/** See {@link BeansWrapper#setDefaultDateType(int)}. */
public void
setDefaultDateType(int
defaultDateType) {
this.
defaultDateType =
defaultDateType;
}
public
ObjectWrapper getOuterIdentity() {
return
outerIdentity;
}
/**
* See {@link BeansWrapper#setOuterIdentity(ObjectWrapper)}, except here the default is {@code null} that means
* the {@link ObjectWrapper} that you will set up with this {@link BeansWrapperBuilder} object.
*/
public void
setOuterIdentity(
ObjectWrapper outerIdentity) {
this.
outerIdentity =
outerIdentity;
}
public boolean
isStrict() {
return
strict;
}
/** See {@link BeansWrapper#setStrict(boolean)}. */
public void
setStrict(boolean
strict) {
this.
strict =
strict;
}
public boolean
getUseModelCache() {
return
useModelCache;
}
/** See {@link BeansWrapper#setUseCache(boolean)} (it means the same). */
public void
setUseModelCache(boolean
useModelCache) {
this.
useModelCache =
useModelCache;
}
public
Version getIncompatibleImprovements() {
return
incompatibleImprovements;
}
public int
getExposureLevel() {
return
classIntrospectorBuilder.
getExposureLevel();
}
/** See {@link BeansWrapper#setExposureLevel(int)}. */
public void
setExposureLevel(int
exposureLevel) {
classIntrospectorBuilder.
setExposureLevel(
exposureLevel);
}
public boolean
getExposeFields() {
return
classIntrospectorBuilder.
getExposeFields();
}
/** See {@link BeansWrapper#setExposeFields(boolean)}. */
public void
setExposeFields(boolean
exposeFields) {
classIntrospectorBuilder.
setExposeFields(
exposeFields);
}
public boolean
getTreatDefaultMethodsAsBeanMembers() {
return
classIntrospectorBuilder.
getTreatDefaultMethodsAsBeanMembers();
}
/** See {@link BeansWrapper#setTreatDefaultMethodsAsBeanMembers(boolean)} */
public void
setTreatDefaultMethodsAsBeanMembers(boolean
treatDefaultMethodsAsBeanMembers) {
classIntrospectorBuilder.
setTreatDefaultMethodsAsBeanMembers(
treatDefaultMethodsAsBeanMembers);
}
public
MethodAppearanceFineTuner getMethodAppearanceFineTuner() {
return
classIntrospectorBuilder.
getMethodAppearanceFineTuner();
}
/**
* See {@link BeansWrapper#setMethodAppearanceFineTuner(MethodAppearanceFineTuner)}; additionally,
* note that currently setting this to non-{@code null} will disable class introspection cache sharing, unless
* the value implements {@link SingletonCustomizer}.
*/
public void
setMethodAppearanceFineTuner(
MethodAppearanceFineTuner methodAppearanceFineTuner) {
classIntrospectorBuilder.
setMethodAppearanceFineTuner(
methodAppearanceFineTuner);
}
MethodSorter getMethodSorter() {
return
classIntrospectorBuilder.
getMethodSorter();
}
void
setMethodSorter(
MethodSorter methodSorter) {
classIntrospectorBuilder.
setMethodSorter(
methodSorter);
}
ClassIntrospectorBuilder getClassIntrospectorBuilder() {
return
classIntrospectorBuilder;
}
}