http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ConfigurationException.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ConfigurationException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ConfigurationException.java new file mode 100644 index 0000000..5b61cca --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ConfigurationException.java @@ -0,0 +1,37 @@ +/* + * 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 org.apache.freemarker.core; + +/** + * Error while configuring FreeMarker. + */ +@SuppressWarnings("serial") +public class ConfigurationException extends RuntimeException { + + public ConfigurationException(String message, Throwable cause) { + super(message, cause); + } + + public ConfigurationException(String message) { + super(message); + } + + +}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ConfigurationSettingValueException.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ConfigurationSettingValueException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ConfigurationSettingValueException.java new file mode 100644 index 0000000..3ed6512 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ConfigurationSettingValueException.java @@ -0,0 +1,86 @@ +/* + * 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 org.apache.freemarker.core; + +import java.util.Date; + +import org.apache.freemarker.core.Configuration.ExtendableBuilder; +import org.apache.freemarker.core.util._StringUtil; + +/** + * Thrown by {@link ExtendableBuilder#setSetting(String, String)}; The setting name was recognized, but its value + * couldn't be parsed or the setting couldn't be set for some other reason. This exception should always have a + * cause exception. + */ +@SuppressWarnings("serial") +public class ConfigurationSettingValueException extends ConfigurationException { + + public ConfigurationSettingValueException(String name, String value, Throwable cause) { + this(name, value, true, null, cause); + } + + public ConfigurationSettingValueException(String name, String value, String reason) { + this(name, value, true, reason, null); + } + + /** + * @param name + * The name of the setting + * @param value + * The value of the setting + * @param showValue + * Whether the value of the setting should be shown in the error message. Set to {@code false} if you want + * to avoid {@link #toString()}-ing the {@code value}. + * @param reason + * The explanation of why setting the setting has failed; maybe {@code null}, especially if you have a cause + * exception anyway. + * @param cause + * The cause exception of this exception (why setting the setting was failed) + */ + public ConfigurationSettingValueException(String name, Object value, boolean showValue, String reason, + Throwable cause) { + super( + createMessage( + name, value, true, + reason != null ? ", because: " : (cause != null ? "; see cause exception." : null), + reason), + cause); + } + + private static String createMessage(String name, Object value, boolean showValue, String detail1, String detail2) { + StringBuilder sb = new StringBuilder(64); + sb.append("Failed to set FreeMarker configuration setting ").append(_StringUtil.jQuote(name)); + if (showValue) { + sb.append(" to value ") + .append( + value instanceof Number || value instanceof Boolean || value instanceof Date ? value + : _StringUtil.jQuote(value)); + } else { + sb.append(" to the specified value"); + } + if (detail1 != null) { + sb.append(detail1); + } + if (detail2 != null) { + sb.append(detail2); + } + return sb.toString(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/CustomStateKey.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/CustomStateKey.java b/freemarker-core/src/main/java/org/apache/freemarker/core/CustomStateKey.java new file mode 100644 index 0000000..fedf096 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/CustomStateKey.java @@ -0,0 +1,60 @@ +/* + * 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 org.apache.freemarker.core; + +/** + * Used with {@link CustomStateScope}-s; each subclass must have exactly one instance, which should be stored in + * a static final field. So the usual usage is like this: + * + * <pre> + * static final CustomStateKey MY_STATE = new CustomStateKey() { + * @Override + * protected Object create() { + * return new ...; + * } + * }; + * </pre> + */ +public abstract class CustomStateKey<T> { + + /** + * This will be invoked when the state for this {@link CustomStateKey} is get via {@link + * CustomStateScope#getCustomState(CustomStateKey)}, but it doesn't yet exists in the given scope. Then the created + * object will be stored in the scope and then it's returned. Must not return {@code null}. + */ + protected abstract T create(); + + /** + * Does identity comparison (like operator {@code ==}). + */ + @Override + final public boolean equals(Object o) { + return o == this; + } + + /** + * Returns {@link Object#hashCode()}. + */ + @Override + final public int hashCode() { + return super.hashCode(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/CustomStateScope.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/CustomStateScope.java b/freemarker-core/src/main/java/org/apache/freemarker/core/CustomStateScope.java new file mode 100644 index 0000000..4067823 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/CustomStateScope.java @@ -0,0 +1,34 @@ +/* + * 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 org.apache.freemarker.core; + +/** + * An object that's a scope that can store custom state objects. + */ +public interface CustomStateScope { + + /** + * Gets the custom state belonging to the key, automatically creating it if it doesn't yet exists in the scope. + * If the scope is {@link Configuration} or {@link Template}, then this method is thread safe. If the scope is + * {@link Environment}, then this method is not thread safe ({@link Environment} is not thread safe either). + */ + <T> T getCustomState(CustomStateKey<T> customStateKey); + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/DirectiveCallPlace.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/DirectiveCallPlace.java b/freemarker-core/src/main/java/org/apache/freemarker/core/DirectiveCallPlace.java new file mode 100644 index 0000000..5793ad3 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/DirectiveCallPlace.java @@ -0,0 +1,137 @@ +/* + * 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 org.apache.freemarker.core; + +import java.util.IdentityHashMap; + +import org.apache.freemarker.core.model.TemplateDirectiveModel; +import org.apache.freemarker.core.model.TemplateTransformModel; +import org.apache.freemarker.core.util.ObjectFactory; + +/** + * Gives information about the place where a directive is called from, also lets you attach a custom data object to that + * place. Each directive call in a template has its own {@link DirectiveCallPlace} object (even when they call the same + * directive with the same parameters). The life cycle of the {@link DirectiveCallPlace} object is bound to the + * {@link Template} object that contains the directive call. Hence, the {@link DirectiveCallPlace} object and the custom + * data you put into it is cached together with the {@link Template} (and templates are normally cached - see + * {@link Configuration#getTemplate(String)}). The custom data is normally initialized on demand, that is, when the + * directive call is first executed, via {@link #getOrCreateCustomData(Object, ObjectFactory)}. + * + * <p> + * Currently this method doesn't give you access to the {@link Template} object, because it's probable that future + * versions of FreeMarker will be able to use the same parsed representation of a "file" for multiple {@link Template} + * objects. Then the call place will be bound to the parsed representation, not to the {@link Template} objects that are + * based on it. + * + * <p> + * <b>Don't implement this interface yourself</b>, as new methods can be added to it any time! It's only meant to be + * implemented by the FreeMarker core. + * + * <p> + * This interface is currently only used for custom directive calls (that is, a {@code <@...>} that calls a + * {@link TemplateDirectiveModel}, {@link TemplateTransformModel}, or a macro). + * + * @see Environment#getCurrentDirectiveCallPlace() + * + * @since 2.3.22 + */ +public interface DirectiveCallPlace { + + /** + * The 1-based column number of the first character of the directive call in the template source code, or -1 if it's + * not known. + */ + int getBeginColumn(); + + /** + * The 1-based line number of the first character of the directive call in the template source code, or -1 if it's + * not known. + */ + int getBeginLine(); + + /** + * The 1-based column number of the last character of the directive call in the template source code, or -1 if it's + * not known. If the directive has an end-tag ({@code </@...>}), then it points to the last character of that. + */ + int getEndColumn(); + + /** + * The 1-based line number of the last character of the directive call in the template source code, or -1 if it's + * not known. If the directive has an end-tag ({@code </@...>}), then it points to the last character of that. + */ + int getEndLine(); + + /** + * Returns the custom data, or if that's {@code null}, then it creates and stores it in an atomic operation then + * returns it. This method is thread-safe, however, it doesn't ensure thread safe (like synchronized) access to the + * custom data itself. See the top-level documentation of {@link DirectiveCallPlace} to understand the scope and + * life-cycle of the custom data. Be sure that the custom data only depends on things that get their final value + * during template parsing, not on runtime settings. + * + * <p> + * This method will block other calls while the {@code objectFactory} is executing, thus, the object will be + * <em>usually</em> created only once, even if multiple threads request the value when it's still {@code null}. It + * doesn't stand though when {@code providerIdentity} mismatches occur (see later). Furthermore, then it's also + * possible that multiple objects created by the same {@link ObjectFactory} will be in use on the same time, because + * of directive executions already running in parallel, and because of memory synchronization delays (hardware + * dependent) between the threads. + * + * @param providerIdentity + * This is usually the class of the {@link TemplateDirectiveModel} that creates (and uses) the custom + * data, or if you are using your own class for the custom data object (as opposed to a class from some + * more generic API), then that class. This is needed as the same call place might calls different + * directives depending on runtime conditions, and so it must be ensured that these directives won't + * accidentally read each other's custom data, ending up with class cast exceptions or worse. In the + * current implementation, if there's a {@code providerIdentity} mismatch (means, the + * {@code providerIdentity} object used when the custom data was last set isn't the exactly same object + * as the one provided with the parameter now), the previous custom data will be just ignored as if it + * was {@code null}. So if multiple directives that use the custom data feature use the same call place, + * the caching of the custom data can be inefficient, as they will keep overwriting each other's custom + * data. (In a more generic implementation the {@code providerIdentity} would be a key in a + * {@link IdentityHashMap}, but then this feature would be slower, while {@code providerIdentity} + * mismatches aren't occurring in most applications.) + * @param objectFactory + * Called when the custom data wasn't yet set, to invoke its initial value. If this parameter is + * {@code null} and the custom data wasn't set yet, then {@code null} will be returned. The returned + * value of {@link ObjectFactory#createObject()} can be any kind of object, but can't be {@code null}. + * + * @return The current custom data object, or possibly {@code null} if there was no {@link ObjectFactory} provided. + * + * @throws CallPlaceCustomDataInitializationException + * If the {@link ObjectFactory} had to be invoked but failed. + */ + Object getOrCreateCustomData(Object providerIdentity, ObjectFactory objectFactory) + throws CallPlaceCustomDataInitializationException; + + /** + * Tells if the nested content (the body) can be safely cached, as it only depends on the template content (not on + * variable values and such) and has no side-effects (other than writing to the output). Examples of cases that give + * {@code false}: {@code <@foo>Name: } <tt>${name}</tt>{@code</@foo>}, + * {@code <@foo>Name: <#if showIt>Joe</#if></@foo>}. Examples of cases that give {@code true}: + * {@code <@foo>Name: Joe</@foo>}, {@code <@foo />}. Note that we get {@code true} for no nested content, because + * that's equivalent with 0-length nested content in FTL. + * + * <p> + * This method returns a pessimistic result. For example, if it sees a custom directive call, it can't know what it + * does, so it will assume that it's not cacheable. + */ + boolean isNestedOutputCacheable(); + +}
