http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/svl/package.html ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/svl/package.html b/juneau-core/src/main/java/org/apache/juneau/svl/package.html new file mode 100644 index 0000000..df73ef0 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/svl/package.html @@ -0,0 +1,246 @@ +<!DOCTYPE HTML> +<!-- +/*************************************************************************************************************************** + * 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. + * + ***************************************************************************************************************************/ + --> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <style type="text/css"> + /* For viewing in Page Designer */ + @IMPORT url("../../../../../../javadoc.css"); + + /* For viewing in REST interface */ + @IMPORT url("../htdocs/javadoc.css"); + body { + margin: 20px; + } + </style> + <script> + /* Replace all @code and @link tags. */ + window.onload = function() { + document.body.innerHTML = document.body.innerHTML.replace(/\{\@code ([^\}]+)\}/g, '<code>$1</code>'); + document.body.innerHTML = document.body.innerHTML.replace(/\{\@link (([^\}]+)\.)?([^\.\}]+)\}/g, '<code>$3</code>'); + } + </script> +</head> +<body> +<p>Simple Variable Language</p> + +<script> + function toggle(x) { + var div = x.nextSibling; + while (div != null && div.nodeType != 1) + div = div.nextSibling; + if (div != null) { + var d = div.style.display; + if (d == 'block' || d == '') { + div.style.display = 'none'; + x.className += " closed"; + } else { + div.style.display = 'block'; + x.className = x.className.replace(/(?:^|\s)closed(?!\S)/g , '' ); + } + } + } +</script> +<a id='TOC'></a><h5 class='toc'>Table of Contents</h5> +<ol class='toc'> + <li><p><a class='doclink' href='#SimpleVarLanguage'>Simple Variable Language</a></p> + <ol> + <li><p><a class='doclink' href='#Vars'>Vars</a></p> + <li><p><a class='doclink' href='#VarResolvers'>VarResolvers and VarResolverSessions</a></p> + <li><p><a class='doclink' href='#OtherNotes'>Other Notes</a></p> + </ol> + </li> +</ol> + +<!-- ======================================================================================================== --> +<a id="SimpleVarLanguage"></a> +<h2 class='topic' onclick='toggle(this)'>1 - Simple Variable Language</h2> +<div class='topic'> + <p> + The <code>org.apache.juneau.svl</code> package defines an API for a language called "Simple Variable Language". + In a nutshell, Simple Variable Language (or SVL) is text that contains variables of the form <js>"$varName{varKey}"</js>. + </p> + <p> + Variables can be recursively nested within the varKey (e.g. <js>"$FOO{$BAR{xxx},$BAZ{xxx}}"</js>). + Variables can also return values that themselves contain more variables. + </p> + <p> + The {@link org.apache.juneau.svl.VarResolver} class is used to resolve variables. + The {@link org.apache.juneau.svl.VarResolver#DEFAULT} resolver will resolve <js>"$S{systemProperty}"</js> and <js>"$E{envVariable}"</js> + variables. + </p> + <p class='bcode'> + <jc>// Use the default variable resolver to resolve a string that contains $S (system property) variables</jc> + String myProperty = VarResolver.<jsf>DEFAULT</jsf>.resolve(<js>"The Java home directory is $S{java.home}"</js>); + </p> + <p> + The following shows how variables can be arbitrarily nested... + </p> + <p class='bcode'> + <jc>// Look up a property in the following order: + // 1) MYPROPERTY environment variable. + // 2) 'my.property' system property if environment variable not found. + // 3) 'not found' string if system property not found.</jc> + String myproperty = VarResolver.<jsf>DEFAULT</jsf>.resolve(<js>"$E{MYPROPERTY,$S{my.property,not found}}"</js>); + </p> + + <!-- ======================================================================================================== --> + <a id="Vars"></a> + <h3 class='topic' onclick='toggle(this)'>1.1 - Vars</h3> + <div class='topic'> + <p> + Variables are defined through the {@link org.apache.juneau.svl.Var} API. + </p> + <p class='bcode'> + <jc>// Create a var resolver that extends the default resolver and appends our own "$URLEncode{...}" variable</jc> + + <jc>// First create our var.</jc> + <jk>public class</jk> UrlEncodeVar <jk>extends</jk> SimpleVar { + + <jc>// Must have a no-arg constructor!</jc> + <jk>public</jk> UrlEncodeVar() { + <jk>super</jk>(<js>"URLEncode"</js>); + } + + <jc>// The method we must implement</jc> + <ja>@Override</ja> + <jk>public</jk> String resolve(VarResolverSession session, String varVal) { + <jk>return</jk> URLEncoder.<jsm>encode</jsm>(varVal, <js>"UTF-8"</js>); + } + } + + <jc>// Next create a var resolver that extends the existing DEFAULT resolver + // that supports resolving system properties.</jc> + VarResolver r = VarResolver.<jsf>DEFAULT</jsf>.clone().addVars(UrlEncodeVar.<jk>class</jk>); + + <jc>// Retrieve a system property and URL-encode it if necessary.</jc> + String myProperty = r.resolve(<js>"$URLEncode{$S{my.property}}"</js>); + </p> + <p> + The following shows the class hierarchy of the {@link org.apache.juneau.svl.Var} class and all current + predefined implementations. + </p> + <ul class='javahierarchy'> + <li class='a'>{@link org.apache.juneau.svl.Var} - Superclass of all vars. + <ul> + <li class='a'>{@link org.apache.juneau.svl.SimpleVar} - Superclass of all vars that return strings. + <ul> + <li class='a'>{@link org.apache.juneau.svl.DefaultingVar} - Variables that define a default value if the resolve method returns null. + <ul> + <li class='a'>{@link org.apache.juneau.svl.MapVar} - Variables that pull values from maps. + <ul> + <li class='c'>{@link org.apache.juneau.svl.vars.SystemPropertiesVar} - Resolves system properties. + </ul> + </li> + <li class='c'>{@link org.apache.juneau.svl.vars.ArgsVar} - Resolves variables from an {@link org.apache.juneau.utils.Args} object. + <li class='c'>{@link org.apache.juneau.svl.vars.ConfigFileVar} - Resolves variables from a {@link org.apache.juneau.ini.ConfigFile} object. + <li class='c'>{@link org.apache.juneau.svl.vars.EnvVariablesVar} - Resolves environment variables. + <li class='c'>{@link org.apache.juneau.svl.vars.ManifestFileVar} - Resolves variables from a {@link org.apache.juneau.utils.ManifestFile} object. + <li class='c'>{@link org.apache.juneau.server.vars.RequestAttrVar} - Resolves HTTP request attribute values. + <li class='c'>{@link org.apache.juneau.server.vars.RequestParamVar} - Resolves HTTP request URL parameter values. + <li class='c'>{@link org.apache.juneau.server.vars.ServletInitParamVar} - Resolves servlet initialization parameters. + </ul> + </li> + <li class='a'>{@link org.apache.juneau.svl.MultipartVar} - Variables that consist of 2 or more comma-delimited arguments. + <ul> + <li class='c'>{@link org.apache.juneau.server.vars.LocalizationVar} - Resolves localized strings for an HTTP request. + </ul> + </li> + <li class='c'>{@link org.apache.juneau.server.vars.RequestVar} - Resolves specialized HTTP request values. + <li class='c'>{@link org.apache.juneau.server.vars.UrlEncodeVar} - URL-encodes the value inside the variable. + </ul> + </li> + <li class='a'>{@link org.apache.juneau.svl.StreamedVar} - Superclass of all vars that stream their value to writers. + <ul> + <li class='c'>{@link org.apache.juneau.server.vars.SerializedRequestAttrVar} - Resolves HTTP request attribute values passed through a {@link org.apache.juneau.serializer.Serializer}. + <li class='c'>{@link org.apache.juneau.server.vars.SerializedRequestParamVar} - Resolves HTTP request URL parameter values passed through a {@link org.apache.juneau.serializer.Serializer}. + </ul> + </li> + </ul> + </li> + </ul> + </div> + + <!-- ======================================================================================================== --> + <a id="VarResolvers"></a> + <h3 class='topic' onclick='toggle(this)'>1.2 - VarResolvers and VarResolverSessions</h3> + <div class='topic'> + <p> + The main class for performing variable resolution is {@link org.apache.juneau.svl.VarResolver}. + Two methods are provided for resolving variables: + </p> + <ul class='javahierarchy'> + <li class='m'>{@link org.apache.juneau.svl.VarResolver#resolve(String)} - Resolves variables and returns the results as a simple string. + <li class='m'>{@link org.apache.juneau.svl.VarResolver#resolveTo(String,Writer)} - Resolves variables and sends results to a writer. + </ul> + <p> + Var resolvers can have zero or more context objects associated with them. + Some {@link org.apache.juneau.svl.Var Vars} rely on the existence of some other object, such as an {@link org.apache.juneau.utils.Args} object + for {@link org.apache.juneau.svl.vars.ArgsVar} or a {@link org.apache.juneau.ini.ConfigFile} for a {@link org.apache.juneau.svl.vars.ConfigFileVar}. + These object dependencies are made by setting context objects on the var resolver. + </p> + <p> + Context objects are set through the {@link org.apache.juneau.svl.VarResolver#setContextObject(String,Object)} method. + They can be any class type. + </p> + <p> + Context objects are used by {@link org.apache.juneau.svl.Var Vars} by calling the {@link org.apache.juneau.svl.VarResolverSession#getSessionObject(Class, String)} method. + </p> + <p> + In addition to context objects, there are also session objects. + Session objects are considered more ephemeral than context objects. + While a context object is unlikely to ever change, a session object may change on every use of the var resolver. + For example, the server API defines various <code>Var</code> objects that use the <code>RestRequest</code> + object as a session object for the duration of a single HTTP request. + </p> + <p> + Session objects are used by calling the {@link org.apache.juneau.svl.VarResolver#createSession()} or {@link org.apache.juneau.svl.VarResolver#createSession(Map)} methods to create an instance + of a {@link org.apache.juneau.svl.VarResolverSession} object that contains {@link org.apache.juneau.svl.VarResolverSession#resolve(String)} and {@link org.apache.juneau.svl.VarResolverSession#resolveTo(String,Writer)} methods + that are identical to {@link org.apache.juneau.svl.VarResolver#resolve(String)} and {@link org.apache.juneau.svl.VarResolver#resolveTo(String, Writer)} except that the <code>Var</code> objects + have access to the session objects through the {@link org.apache.juneau.svl.VarResolverSession#getSessionObject(Class, String)} method. + Session objects are specified through either the {@link org.apache.juneau.svl.VarResolver#createSession(Map)} method or the {@link org.apache.juneau.svl.VarResolverSession#setSessionObject(String, Object)} methods. + </p> + <p> + Like Context object, Session objects are used by {@link org.apache.juneau.svl.Var Vars} by calling the {@link org.apache.juneau.svl.VarResolverSession#getSessionObject(Class, String)} method. + </p> + <p> + Var resolvers can be cloned and extended by using the {@link org.apache.juneau.svl.VarResolver#clone()} method. + Cloning a resolver will copy it's {@link org.apache.juneau.svl.Var} class names and context objects. + </p> + <h6 class='topic'>Example:</h6> + <p class='bcode'> + <jc>// Create a resolver that copies the default resolver and adds $C and $ARG vars.</jc> + VarResolver myVarResolver = VarResolver.<jsf>DEFAULT</jsf>.clone().addVars(ConfigFileVar.<jk>class</jk>, ArgsVar.<jk>class</jk>); + </p> + </div> + + <!-- ======================================================================================================== --> + <a id="OtherNotes"></a> + <h3 class='topic' onclick='toggle(this)'>1.3 - Other Notes</h3> + <div class='topic'> + <ul class='spaced-list'> + <li>The escape character <js>'\'</js> can be used when necessary to escape the following characters: <code>$ , { }</code> + <li><b>WARNING:</b> It is possible to cause {@link java.lang.StackOverflowError StackOverflowErrors} if your nested variables result in + a recursive loop (e.g. the environment variable <code>'MYPROPERTY'</code> has the value <code>'$E{MYPROPERTY}'</code>). + So don't do that! + <li>As a general rule, this class tries to be as efficient as possible by not creating new strings when not needed.<br> + For example, calling the resolve method on a string that doesn't contain variables (e.g. <code>resolver.resolve(<js>"foobar"</js>)</code>) + will simply be a no-op and return the same string. + </ul> + </div> +</body> +</html> \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/svl/vars/ArgsVar.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/svl/vars/ArgsVar.java b/juneau-core/src/main/java/org/apache/juneau/svl/vars/ArgsVar.java new file mode 100644 index 0000000..f1c9f38 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/svl/vars/ArgsVar.java @@ -0,0 +1,64 @@ +/*************************************************************************************************************************** + * 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.juneau.svl.vars; + +import org.apache.juneau.svl.*; +import org.apache.juneau.utils.*; + +/** + * JVM args variable resolver. + * <p> + * The format for this var is <js>"$ARG{argNameOrNum}"</js> or <js>"$ARG{argNameOrNum,defaultValue}"</js> + * <p> + * This variable resolver requires that an {@link Args} object be set as a context object on the resolver or a + * session object on the resolver session. + * + * <h6 class='topic'>Example:</h6> + * <p class='bcode'> + * <jc>// Create an args object from the main(String[]) method.</jc> + * Args args = new Args(argv); + * + * <jc>// Create a variable resolver that resolves JVM arguments (e.g. "$ARG{1}")</jc> + * VarResolver r = <jk>new</jk> VarResolver().addVars(ArgsVar.<js>class</js>).addContextObject(<jsf>SESSION_args</jsf>, args); + * + * <jc>// Use it!</jc> + * System.<jsf>out</jsf>.println(r.resolve(<js>"Arg #1 is set to $ARG{1}"</js>)); + * </p> + * <p> + * Since this is a {@link SimpleVar}, any variables contained in the result will be recursively resolved. + * Likewise, if the arguments contain any variables, those will be resolved before they are passed to this var. + * + * @see org.apache.juneau.utils.Args + * @see org.apache.juneau.svl + * @author James Bognar ([email protected]) + */ +public class ArgsVar extends DefaultingVar { + + /** + * The name of the session or context object that identifies the {@link Args} object. + */ + public static final String SESSION_args = "args"; + + /** + * Constructor. + */ + public ArgsVar() { + super("ARG"); + } + + @Override /* Var */ + public String resolve(VarResolverSession session, String key) { + return session.getSessionObject(Args.class, SESSION_args).getArg(key); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/svl/vars/ConfigFileVar.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/svl/vars/ConfigFileVar.java b/juneau-core/src/main/java/org/apache/juneau/svl/vars/ConfigFileVar.java new file mode 100644 index 0000000..e69bda1 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/svl/vars/ConfigFileVar.java @@ -0,0 +1,65 @@ +/*************************************************************************************************************************** + * 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.juneau.svl.vars; + +import org.apache.juneau.ini.*; +import org.apache.juneau.svl.*; + +/** + * Config file variable resolver. + * <p> + * The format for this var is <js>"$C{key}"</js> or <js>"$C{key,defaultValue}"</js>. + * See {@link ConfigFile#getString(String)} for the format of the key. + * <p> + * This variable resolver requires that a {@link ConfigFile} object be set as a context object on the resolver or a + * session object on the resolver session. + * + * <h6 class='topic'>Example:</h6> + * <p class='bcode'> + * <jc>// Create a config file object.</jc> + * ConfigFile configFile = ConfigMgr.<jsf>DEFAULT</jsf>.get(<js>"MyConfig.cfg"</js>); + * + * <jc>// Create a variable resolver that resolves config file entries (e.g. "$C{MySection/myKey}")</jc> + * VarResolver r = <jk>new</jk> VarResolver().addVars(ConfigVar.<js>class</js>).addContextObject(<jsf>SESSION_config</jsf>, configFile); + * + * <jc>// Use it!</jc> + * System.<jsf>out</jsf>.println(r.resolve(<js>"Value for myKey in section MySection is $C{MySection/myKey}"</js>)); + * </p> + * <p> + * Since this is a {@link SimpleVar}, any variables contained in the result will be recursively resolved. + * Likewise, if the arguments contain any variables, those will be resolved before they are passed to this var. + * + * @see org.apache.juneau.ini.ConfigFile + * @see org.apache.juneau.svl + * @author James Bognar ([email protected]) + */ +public class ConfigFileVar extends DefaultingVar { + + /** + * The name of the session or context object that identifies the {@link ConfigFile} object. + */ + public static final String SESSION_config = "config"; + + /** + * Constructor. + */ + public ConfigFileVar() { + super("C"); + } + + @Override /* Var */ + public String resolve(VarResolverSession session, String key) { + return session.getSessionObject(ConfigFile.class, SESSION_config).getString(key); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/svl/vars/EnvVariablesVar.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/svl/vars/EnvVariablesVar.java b/juneau-core/src/main/java/org/apache/juneau/svl/vars/EnvVariablesVar.java new file mode 100644 index 0000000..e2f8024 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/svl/vars/EnvVariablesVar.java @@ -0,0 +1,52 @@ +/*************************************************************************************************************************** + * 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.juneau.svl.vars; + +import org.apache.juneau.svl.*; + +/** + * Environment variable variable resolver. + * <p> + * The format for this var is <js>"$E{envVar}"</js> or <js>"$E{envVar,defaultValue}"</js> + * + * <h6 class='topic'>Example:</h6> + * <p class='bcode'> + * <jc>// Create a variable resolver that resolves environment variables (e.g. "$E{PATH}")</jc> + * VarResolver r = <jk>new</jk> VarResolver().addVars(EnvVariablesVar.<js>class</js>); + * + * <jc>// Use it!</jc> + * System.<jsf>out</jsf>.println(r.resolve(<js>"Environment variable PATH is set to $E{PATH}"</js>)); + * </p> + * <p> + * Since this is a {@link SimpleVar}, any variables contained in the result will be recursively resolved. + * Likewise, if the arguments contain any variables, those will be resolved before they are passed to this var. + * + * @see org.apache.juneau.svl + * @author James Bognar ([email protected]) + */ +public class EnvVariablesVar extends DefaultingVar { + + /** + * Constructor. + */ + public EnvVariablesVar() { + super("E"); + } + + @Override /* Var */ + public String resolve(VarResolverSession session, String varVal) { + // Note that lookup is case-insensitive on windows. + return System.getenv(varVal); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/svl/vars/ManifestFileVar.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/svl/vars/ManifestFileVar.java b/juneau-core/src/main/java/org/apache/juneau/svl/vars/ManifestFileVar.java new file mode 100644 index 0000000..ccfbee7 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/svl/vars/ManifestFileVar.java @@ -0,0 +1,64 @@ +/*************************************************************************************************************************** + * 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.juneau.svl.vars; + +import org.apache.juneau.svl.*; +import org.apache.juneau.utils.*; + +/** + * Manifest file entries variable resolver. + * <p> + * The format for this var is <js>"$MF{key}"</js> or <js>"$MF{key,defaultValue}"</js> + * <p> + * This variable resolver requires that a {@link ManifestFile} object be set as a context object on the resolver or a + * session object on the resolver session. + * + * <h6 class='topic'>Example:</h6> + * <p class='bcode'> + * <jc>// Create a ManifestFile object that contains the manifest of the jar file containing this class.</jc> + * ManifestFile mf = <jk>new</jk> ManifestFile(<jk>this</jk>.getClass()); + * + * <jc>// Create a variable resolver that resolves manifest file entries (e.g. "$MF{Main-Class}")</jc> + * VarResolver r = <jk>new</jk> VarResolver().addVars(ManifestFile.<js>class</js>).addContextObject(<jsf>SESSION_manifest</jsf>, mf); + * + * <jc>// Use it!</jc> + * System.<jsf>out</jsf>.println(r.resolve(<js>"The main class is $MF{Main-Class}"</js>)); + * </p> + * <p> + * Since this is a {@link SimpleVar}, any variables contained in the result will be recursively resolved. + * Likewise, if the arguments contain any variables, those will be resolved before they are passed to this var. + * + * @see org.apache.juneau.utils.ManifestFile + * @see org.apache.juneau.svl + * @author James Bognar ([email protected]) + */ +public class ManifestFileVar extends DefaultingVar { + + /** + * The name of the session or context object that identifies the {@link ManifestFile} object. + */ + public static final String SESSION_manifest = "manifest"; + + /** + * Constructor. + */ + public ManifestFileVar() { + super("MF"); + } + + @Override /* Var */ + public String resolve(VarResolverSession session, String key) { + return session.getSessionObject(ManifestFile.class, SESSION_manifest).getString(key); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/svl/vars/SystemPropertiesVar.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/svl/vars/SystemPropertiesVar.java b/juneau-core/src/main/java/org/apache/juneau/svl/vars/SystemPropertiesVar.java new file mode 100644 index 0000000..a2aa1f7 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/svl/vars/SystemPropertiesVar.java @@ -0,0 +1,46 @@ +/*************************************************************************************************************************** + * 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.juneau.svl.vars; + +import org.apache.juneau.svl.*; + +/** + * System property variable resolver. + * <p> + * The format for this var is <js>"$S{systemProperty}"</js> or <js>"$S{systemProperty,defaultValue}"</js> + * + * <h6 class='topic'>Example:</h6> + * <p class='bcode'> + * <jc>// Create a variable resolver that resolves system properties (e.g. "$S{java.home}")</jc> + * VarResolver r = <jk>new</jk> VarResolver().addVars(SystemPropertiesVar.<js>class</js>); + * + * <jc>// Use it!</jc> + * System.<jsf>out</jsf>.println(r.resolve(<js>"java.home is set to $S{java.home}"</js>)); + * </p> + * <p> + * Since this is a {@link SimpleVar}, any variables contained in the result will be recursively resolved. + * Likewise, if the arguments contain any variables, those will be resolved before they are passed to this var. + * + * @see org.apache.juneau.svl + * @author James Bognar ([email protected]) + */ +public class SystemPropertiesVar extends MapVar { + + /** + * Constructor. + */ + public SystemPropertiesVar() { + super("S", System.getProperties()); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/transform/AnnotationBeanTransform.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/transform/AnnotationBeanTransform.java b/juneau-core/src/main/java/org/apache/juneau/transform/AnnotationBeanTransform.java new file mode 100644 index 0000000..a6bc86f --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/transform/AnnotationBeanTransform.java @@ -0,0 +1,70 @@ +/*************************************************************************************************************************** + * 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.juneau.transform; + +import java.util.*; + +import org.apache.juneau.annotation.*; + +/** + * Bean transform constructed from a {@link Bean @Bean} annotation found on a class. + * <p> + * <b>*** Internal class - Not intended for external use ***</b> + * + * @author James Bognar ([email protected]) + * @param <T> The class type that this transform applies to. + */ +public final class AnnotationBeanTransform<T> extends BeanTransform<T> { + + /** + * Constructor. + * + * @param annotatedClass The class found to have a {@link Bean @Bean} annotation. + * @param annotations The {@link Bean @Bean} annotations found on the class and all parent classes in child-to-parent order. + */ + public AnnotationBeanTransform(Class<T> annotatedClass, List<Bean> annotations) { + super(annotatedClass); + + ListIterator<Bean> li = annotations.listIterator(annotations.size()); + while (li.hasPrevious()) { + Bean b = li.previous(); + + if (b.properties().length > 0 && getProperties() == null) + setProperties(b.properties()); + + if (b.sort()) + setSortProperties(true); + + if (b.excludeProperties().length > 0) + setExcludeProperties(b.excludeProperties()); + + setPropertyNamer(b.propertyNamer()); + + if (b.interfaceClass() != Object.class) + setInterfaceClass(b.interfaceClass()); + + if (b.stopClass() != Object.class) + setStopClass(b.stopClass()); + + if (! b.subTypeProperty().isEmpty()) { + setSubTypeProperty(b.subTypeProperty()); + + LinkedHashMap<Class<?>,String> subTypes = new LinkedHashMap<Class<?>,String>(); + for (BeanSubType bst : b.subTypes()) + subTypes.put(bst.type(), bst.id()); + + setSubTypes(subTypes); + } + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/transform/BeanTransform.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/transform/BeanTransform.java b/juneau-core/src/main/java/org/apache/juneau/transform/BeanTransform.java new file mode 100644 index 0000000..f0b9f3c --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/transform/BeanTransform.java @@ -0,0 +1,526 @@ +/*************************************************************************************************************************** + * 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.juneau.transform; + +import java.beans.*; +import java.lang.reflect.*; +import java.util.*; + +import org.apache.juneau.*; +import org.apache.juneau.annotation.*; +import org.apache.juneau.internal.*; + +/** + * Parent class for all bean transforms. + * <p> + * Bean transforms are used to control aspects of how beans are handled during serialization and parsing. + * <p> + * This class can be considered a programmatic equivalent to using the {@link Bean @Bean} annotation on bean classes. + * Thus, it can be used to perform the same function as the <code>@Bean</code> annotation when you don't have + * the ability to annotate those classes (e.g. you don't have access to the source code). + * <p> + * Note that value returned by the {@link Transform#forClass()} method is automatically determined through reflection + * when the no-arg constructor is used. + * + * <p> + * When defining bean transforms, you can either call the setters in the contructor, or override getters. + * + * <h6 class='topic'>Example</h6> + * <p class='bcode'> + * <jc>// Create our serializer with a transform.</jc> + * WriterSerializer s = <jk>new</jk> JsonSerializer().addTransforms(AddressTransform.<jk>class</jk>); + * + * Address a = <jk>new</jk> Address(); + * String json = s.serialize(a); <jc>// Serializes only street, city, state.</jc> + * + * <jc>// Transform class defined via setters</jc> + * <jk>public class</jk> AddressTransform <jk>extends</jk> BeanTransform<Address> { + * <jk>public</jk> AddressTransform() { + * setProperties(<js>"street"</js>,<js>"city"</js>,<js>"state"</js>); + * } + * } + * + * <jc>// Transform class defined by overriding getters</jc> + * <jk>public class</jk> AddressTransform <jk>extends</jk> BeanTransform<Address> { + * <jk>public</jk> String[] getProperties() { + * <jk>return new</jk> String[]{<js>"street"</js>,<js>"city"</js>,<js>"state"</js>}; + * } + * } + * </p> + * <p> + * The examples in this class use the setters approach. + * + * <h6 class='topic'>Additional information</h6> + * See {@link org.apache.juneau.transform} for more information. + * + * + * @author James Bognar ([email protected]) + * @param <T> The class type that this transform applies to. + */ +public abstract class BeanTransform<T> extends Transform { + + private String[] properties, excludeProperties; + private LinkedHashMap<Class<?>, String> subTypes; + private String subTypeAttr; + private Class<? extends PropertyNamer> propertyNamer; + private Class<?> interfaceClass, stopClass; + private boolean sortProperties; + + /** + * Constructor that determines the for-class value using reflection. + */ + @SuppressWarnings("unchecked") + public BeanTransform() { + super(); + this.type = TransformType.BEAN; + + Class<?> c = this.getClass().getSuperclass(); + Type t = this.getClass().getGenericSuperclass(); + while (c != BeanTransform.class) { + t = c.getGenericSuperclass(); + c = c.getSuperclass(); + } + + // Attempt to determine the T and G classes using reflection. + if (t instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType)t; + Type[] pta = pt.getActualTypeArguments(); + if (pta.length > 0) { + Type nType = pta[0]; + if (nType instanceof Class) + this.forClass = (Class<T>)nType; + + else + throw new RuntimeException("Unsupported parameter type: " + nType); + } + } + } + + /** + * Constructor that specifies the for-class explicitly. + * <p> + * This constructor only needs to be called when the class type cannot be inferred through reflection. + * + * <dl> + * <dt>Example:</dt> + * <dd> + * <p class='bcode'> + * <jk>public class</jk> SomeArbitraryTransform <jk>extends</jk> BeanTransform<?> { + * <jk>public</jk> SomeArbitraryTransform(Class<?> forClass) { + * <jk>super</jk>(forClass); + * ... + * } + * } + * </p> + * </dd> + * </dl> + * + * @param forClass The class that this bean transform applies to. + */ + public BeanTransform(Class<T> forClass) { + super(forClass); + this.type = TransformType.BEAN; + } + + /** + * Returns the set and order of names of properties associated with a bean class. + * + * @see #setProperties(String...) + * @return The name of the properties associated with a bean class, or <jk>null</jk> if all bean properties should be used. + */ + public String[] getProperties() { + return properties; + } + + /** + * Specifies the set and order of names of properties associated with a bean class. + * <p> + * The order specified is the same order that the entries will be returned by the {@link BeanMap#entrySet()} and related methods. + * <p> + * This method is an alternative to using the {@link Bean#properties()} annotation on a class. + * + * <dl> + * <dt>Example:</dt> + * <dd> + * <p class='bcode'> + * <jc>// Create our serializer with a transform.</jc> + * WriterSerializer s = <jk>new</jk> JsonSerializer().addTransforms(AddressTransform.<jk>class</jk>); + * + * Address a = <jk>new</jk> Address(); + * String json = s.serialize(a); <jc>// Serializes only street, city, state.</jc> + * + * <jc>// Transform class</jc> + * <jk>public class</jk> AddressTransform <jk>extends</jk> BeanTransform<Address> { + * <jk>public</jk> AddressTransform() { + * setProperties(<js>"street"</js>,<js>"city"</js>,<js>"state"</js>); + * } + * } + * </p> + * </dd> + * </dl> + * + * @param properties The name of the properties associated with a bean class. + * @return This object (for method chaining). + */ + public BeanTransform<T> setProperties(String...properties) { + this.properties = properties; + return this; + } + + /** + * Same as {@link #setProperties(String[])} but pass in a comma-delimited list of values. + * + * @param properties A comma-delimited list of properties. + * @return This object (for method chaining). + */ + public BeanTransform<T> setProperties(String properties) { + return setProperties(StringUtils.split(properties, ',')); + } + + /** + * Returns <jk>true</jk> if the properties defined on this bean class should be ordered alphabetically. + * <p> + * This method is only used when the {@link #getProperties()} method returns <jk>null</jk>. + * Otherwise, the ordering of the properties in the returned value is used. + * + * @see #setSortProperties(boolean) + * @return <jk>true</jk> if bean properties should be sorted. + */ + public boolean isSortProperties() { + return sortProperties; + } + + /** + * Specifies whether the properties on this bean should be ordered alphabetically. + * <p> + * This method is ignored if the {@link #getProperties()} method does not return <jk>null</jk>. + * <p> + * This method is an alternative to using the {@link Bean#sort()} annotation on a class. + * + * @param sortProperties The new value for the sort properties property. + * @return This object (for method chaining). + */ + public BeanTransform<T> setSortProperties(boolean sortProperties) { + this.sortProperties = sortProperties; + return this; + } + + /** + * Returns the list of properties to ignore on a bean. + * + * @see #setExcludeProperties(String...) + * @return The name of the properties to ignore on a bean, or <jk>null</jk> to not ignore any properties. + */ + public String[] getExcludeProperties() { + return excludeProperties; + } + + /** + * Specifies a list of properties to ignore on a bean. + * <p> + * This method is an alternative to using the {@link Bean#excludeProperties()} annotation on a class. + * + * <dl> + * <dt>Example:</dt> + * <dd> + * <p class='bcode'> + * <jc>// Create our serializer with a transform.</jc> + * WriterSerializer s = <jk>new</jk> JsonSerializer().addTransforms(NoCityOrStateTransform.<jk>class</jk>); + * + * Address a = <jk>new</jk> Address(); + * String json = s.serialize(a); <jc>// Excludes city and state.</jc> + * + * <jc>// Transform class</jc> + * <jk>public class</jk> NoCityOrStateTransform <jk>extends</jk> BeanTransform<Address> { + * <jk>public</jk> AddressTransform() { + * setExcludeProperties(<js>"city"</js>,<js>"state"</js>); + * } + * } + * </p> + * </dd> + * </dl> + * + * @param excludeProperties The name of the properties to ignore on a bean class. + * @return This object (for method chaining). + */ + public BeanTransform<T> setExcludeProperties(String...excludeProperties) { + this.excludeProperties = excludeProperties; + return this; + } + + /** + * Same as {@link #setExcludeProperties(String[])} but pass in a comma-delimited list of values. + * + * @param excludeProperties A comma-delimited list of properties to eclipse. + * @return This object (for method chaining). + */ + public BeanTransform<T> setExcludeProperties(String excludeProperties) { + return setExcludeProperties(StringUtils.split(excludeProperties, ',')); + } + + /** + * Returns the {@link PropertyNamer} associated with the bean to tailor the names of bean properties. + * + * @see #setPropertyNamer(Class) + * @return The property namer class, or <jk>null</jk> if no property namer is associated with this bean property. + */ + public Class<? extends PropertyNamer> getPropertyNamer() { + return propertyNamer; + } + + /** + * Associates a {@link PropertyNamer} with this bean to tailor the names of the bean properties. + * <p> + * Property namers are used to transform bean property names from standard form to some other form. + * For example, the {@link PropertyNamerDashedLC} will convert property names to dashed-lowercase, and + * these will be used as attribute names in JSON, and element names in XML. + * <p> + * This method is an alternative to using the {@link Bean#propertyNamer()} annotation on a class. + * + * @param propertyNamer The property namer class. + * @return This object (for method chaining). + */ + public BeanTransform<T> setPropertyNamer(Class<? extends PropertyNamer> propertyNamer) { + this.propertyNamer = propertyNamer; + return this; + } + + /** + * Returns the name of the sub type property associated with the bean class. + * + * @see #setSubTypeProperty(String) + * @return The sub type property name, or <jk>null</jk> if bean has no subtypes defined. + */ + public String getSubTypeProperty() { + return subTypeAttr; + } + + /** + * Defines a virtual property on a superclass that identifies bean subtype classes. + * <p> + * In the following example, the abstract class has two subclasses that are differentiated + * by a property called <code>subType</code> + * + * <p class='bcode'> + * <jc>// Abstract superclass</jc> + * <jk>public abstract class</jk> A { + * <jk>public</jk> String <jf>f0</jf> = <js>"f0"</js>; + * } + * + * <jc>// Subclass 1</jc> + * <jk>public class</jk> A1 <jk>extends</jk> A { + * <jk>public</jk> String <jf>f1</jf>; + * } + * + * <jc>// Subclass 2</jc> + * <jk>public class</jk> A2 <jk>extends</jk> A { + * <jk>public</jk> String <jf>f2</jf>; + * } + * + * <jc>// Transform for defining subtypes</jc> + * <jk>public class</jk> ATransform <jk>extends</jk> BeanTransform<A> { + * <jk>public</jk> ATransform() { + * setSubTypeProperty(<js>"subType"</js>); + * addSubType(Al.<jk>class</jk>, <js>"A1"</js>); + * addSubType(A2.<jk>class</jk>, <js>"A2"</js>); + * } + * } + * </p> + * <p> + * The following shows what happens when serializing a subclassed object to JSON: + * <p class='bcode'> + * JsonSerializer s = <jk>new</jk> JsonSerializer().addTransforms(ATransform.<jk>class</jk>); + * A1 a1 = <jk>new</jk> A1(); + * a1.<jf>f1</jf> = <js>"f1"</js>; + * String r = s.serialize(a1); + * <jsm>assertEquals</jsm>(<js>"{subType:'A1',f1:'f1',f0:'f0'}"</js>, r); + * </p> + * <p> + * The following shows what happens when parsing back into the original object. + * <p class='bcode'> + * JsonParser p = <jk>new</jk> JsonParser().addTransforms(ATransform.<jk>class</jk>); + * A a = p.parse(r, A.<jk>class</jk>); + * <jsm>assertTrue</jsm>(a <jk>instanceof</jk> A1); + * </p> + * <p> + * This method is an alternative to using the {@link Bean#subTypeProperty()} annotation on a class. + * + * @param subTypeAttr The name of the attribute representing the subtype. + * @return This object (for method chaining). + */ + public BeanTransform<T> setSubTypeProperty(String subTypeAttr) { + this.subTypeAttr = subTypeAttr; + return this; + } + + /** + * Returns the subtypes associated with the bean class. + * + * @see #setSubTypeProperty(String) + * @return The set of sub types associated with this bean class, or <jk>null</jk> if bean has no subtypes defined. + */ + public LinkedHashMap<Class<?>, String> getSubTypes() { + return subTypes; + } + + /** + * Specifies the set of subclasses of this bean class in addition to a string identifier for that subclass. + * + * @see #setSubTypeProperty(String) + * @param subTypes the map of subtype classes to subtype identifier strings. + * @return This object (for method chaining). + */ + public BeanTransform<T> setSubTypes(LinkedHashMap<Class<?>, String> subTypes) { + this.subTypes = subTypes; + return this; + } + + /** + * Convenience method for adding a single subtype in leu of using {@link #setSubTypes(LinkedHashMap)} in one call. + * + * @see #setSubTypeProperty(String) + * @param c The subtype class. + * @param id The subtype identifier string for the specified subtype class. + * @return This object (for method chaining). + */ + public BeanTransform<T> addSubType(Class<?> c, String id) { + if (subTypes == null) + subTypes = new LinkedHashMap<Class<?>, String>(); + subTypes.put(c, id); + return this; + } + + /** + * Returns the interface class associated with this class. + * + * @see #setInterfaceClass(Class) + * @return The interface class associated with this class, or <jk>null</jk> if no interface class is associated. + */ + public Class<?> getInterfaceClass() { + return interfaceClass; + } + + /** + * Identifies a class to be used as the interface class for this and all subclasses. + * <p> + * Functionally equivalent to using the {@link Bean#interfaceClass()} annotation. + * <p> + * When specified, only the list of properties defined on the interface class will be used during serialization. + * Additional properties on subclasses will be ignored. + * <p class='bcode'> + * <jc>// Parent class</jc> + * <jk>public abstract class</jk> A { + * <jk>public</jk> String <jf>f0</jf> = <js>"f0"</js>; + * } + * + * <jc>// Sub class</jc> + * <jk>public class</jk> A1 <jk>extends</jk> A { + * <jk>public</jk> String <jf>f1</jf> = <js>"f1"</js>; + * } + * + * <jc>// Transform class</jc> + * <jk>public class</jk> ATransform <jk>extends</jk> BeanTransform<A> { + * <jk>public</jk> ATransform() { + * setInterfaceClass(A.<jk>class</jk>); + * } + * } + * + * JsonSerializer s = new JsonSerializer().addTransforms(ATransform.<jk>class</jk>); + * A1 a1 = <jk>new</jk> A1(); + * String r = s.serialize(a1); + * <jsm>assertEquals</jsm>(<js>"{f0:'f0'}"</js>, r); <jc>// Note f1 is not serialized</jc> + * </p> + * <p> + * Note that this transform can be used on the parent class so that it transforms to all child classes, + * or can be set individually on the child classes. + * <p> + * This method is an alternative to using the {@link Bean#interfaceClass()}} annotation. + * + * @param interfaceClass The interface class. + * @return This object (for method chaining). + */ + public BeanTransform<T> setInterfaceClass(Class<?> interfaceClass) { + this.interfaceClass = interfaceClass; + return this; + } + + /** + * Returns the stop class associated with this class. + * + * @see #setStopClass(Class) + * @return The stop class associated with this class, or <jk>null</jk> if no stop class is associated. + */ + public Class<?> getStopClass() { + return stopClass; + } + + /** + * Identifies a stop class for this class and all subclasses. + * <p> + * Functionally equivalent to using the {@link Bean#stopClass()} annotation. + * <p> + * Identical in purpose to the stop class specified by {@link Introspector#getBeanInfo(Class, Class)}. + * Any properties in the stop class or in its baseclasses will be ignored during analysis. + * <p> + * For example, in the following class hierarchy, instances of <code>C3</code> will include property <code>p3</code>, but + * not <code>p1</code> or <code>p2</code>. + * <p class='bcode'> + * <jk>public class</jk> C1 { + * <jk>public int</jk> getP1(); + * } + * + * <jk>public class</jk> C2 <jk>extends</jk> C1 { + * <jk>public int</jk> getP2(); + * } + * + * <ja>@Bean</ja>(stopClass=C2.<jk>class</jk>) + * <jk>public class</jk> C3 <jk>extends</jk> C2 { + * <jk>public int</jk> getP3(); + * } + * </p> + * + * @param stopClass The stop class. + * @return This object (for method chaining). + */ + public BeanTransform<T> setStopClass(Class<?> stopClass) { + this.stopClass = stopClass; + return this; + } + + /** + * Subclasses can override this property to convert property values to some other + * object just before serialization. + * + * @param bean The bean from which the property was read. + * @param name The property name. + * @param value The value just extracted from calling the bean getter. + * @return The value to serialize. Default is just to return the existing value. + */ + public Object readProperty(Object bean, String name, Object value) { + return value; + } + + /** + * Subclasses can override this property to convert property values to some other + * object just before calling the bean setter. + * + * @param bean The bean from which the property was read. + * @param name The property name. + * @param value The value just parsed. + * @return <jk>true</jk> if we set the property, <jk>false</jk> if we should allow the + * framework to call the setter. + */ + public boolean writeProperty(Object bean, String name, Object value) { + return false; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/transform/InterfaceBeanTransform.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/transform/InterfaceBeanTransform.java b/juneau-core/src/main/java/org/apache/juneau/transform/InterfaceBeanTransform.java new file mode 100644 index 0000000..5f281f3 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/transform/InterfaceBeanTransform.java @@ -0,0 +1,39 @@ +/*************************************************************************************************************************** + * 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.juneau.transform; + +import org.apache.juneau.*; + + +/** + * Simple bean transform that simply identifies a class to be used as an interface + * class for all child classes. + * <p> + * These objects are created when you pass in non-<code>Transform</code> classes to {@link ContextFactory#addToProperty(String,Object)}, + * and are equivalent to adding a <code><ja>@Bean</ja>(interfaceClass=Foo.<jk>class</jk>)</code> annotation on the <code>Foo</code> class. + * + * @author James Bognar ([email protected]) + * @param <T> The class type that this transform applies to. + */ +public class InterfaceBeanTransform<T> extends BeanTransform<T> { + + /** + * Constructor. + * + * @param interfaceClass The class to use as an interface on all child classes. + */ + public InterfaceBeanTransform(Class<T> interfaceClass) { + super(interfaceClass); + setInterfaceClass(interfaceClass); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/transform/PojoTransform.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/transform/PojoTransform.java b/juneau-core/src/main/java/org/apache/juneau/transform/PojoTransform.java new file mode 100644 index 0000000..4d41b3f --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/transform/PojoTransform.java @@ -0,0 +1,265 @@ +/*************************************************************************************************************************** + * 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.juneau.transform; + +import java.lang.reflect.*; +import java.util.*; + +import org.apache.juneau.*; +import org.apache.juneau.internal.*; +import org.apache.juneau.parser.*; +import org.apache.juneau.serializer.*; + +/** + * Used to convert non-serializable objects to a serializable form. + * + * + * <h6 class='topic'>Description</h6> + * <p> + * <code>PojoTransforms</code> are used to extend the functionality of the serializers and parsers to be able to handle POJOs + * that aren't automatically handled by the serializers or parsers. For example, JSON does not have a standard + * representation for rendering dates. By defining a special {@code Date} transform and associating it with a serializer and + * parser, you can convert a {@code Date} object to a {@code String} during serialization, and convert that {@code String} object back into + * a {@code Date} object during parsing. + * <p> + * Object transforms MUST declare a public no-arg constructor so that the bean context can instantiate them. + * <p> + * <code>PojoTransforms</code> are associated with instances of {@link BeanContext BeanContexts} by passing the transform class to + * the {@link CoreApi#addTransforms(Class...)} method.<br> + * When associated with a bean context, fields of the specified type will automatically be converted when the + * {@link BeanMap#get(Object)} or {@link BeanMap#put(String, Object)} methods are called.<br> + * <p> + * <code>PojoTransforms</code> have two parameters: + * <ol> + * <li>{@code <F>} - The transformed representation of an object. + * <li>{@code <T>} - The normal representation of an object. + * </ol> + * <br> + * {@link Serializer Serializers} use object transforms to convert objects of type T into objects of type F, and on calls to {@link BeanMap#get(Object)}.<br> + * {@link Parser Parsers} use object transforms to convert objects of type F into objects of type T, and on calls to {@link BeanMap#put(String,Object)}. + * + * + * <h6 class='topic'>Transformed Class Type {@code <T>}</h6> + * <p> + * The transformed object representation of an object must be an object type that the serializers can + * natively convert to JSON (or language-specific equivalent). The list of valid transformed types are as follows... + * <ul class='spaced-list'> + * <li>{@link String} + * <li>{@link Number} + * <li>{@link Boolean} + * <li>{@link Collection} containing anything on this list. + * <li>{@link Map} containing anything on this list. + * <li>A java bean with properties of anything on this list. + * <li>An array of anything on this list. + * </ul> + * + * + * <h6 class='topic'>Normal Class Type {@code <N>}</h6> + * <p> + * The normal object representation of an object.<br> + * + * + * <h6 class='topic'>One-way vs. Two-way Serialization</h6> + * <p> + * Note that while there is a unified interface for handling transforming during both serialization and parsing, + * in many cases only one of the {@link #transform(Object)} or {@link #normalize(Object, ClassMeta)} methods will be defined + * because the transform is one-way. For example, a transform may be defined to convert an {@code Iterator} to a {@code ObjectList}, but + * it's not possible to untransform an {@code Iterator}. In that case, the {@code generalize(Object}} method would + * be implemented, but the {@code narrow(ObjectMap)} object would not, and the transform would be associated on + * the serializer, but not the parser. Also, you may choose to serialize objects like {@code Dates} to readable {@code Strings}, + * in which case it's not possible to reparse it back into a {@code Date}, since there is no way for the {@code Parser} to + * know it's a {@code Date} from just the JSON or XML text. + * + * + * <h6 class='topic'>Additional information</h6> + * See {@link org.apache.juneau.transform} for more information. + * + * + * @author James Bognar ([email protected]) + * @param <N> The normal form of the class. + * @param <T> The transformed form of the class. + */ +public abstract class PojoTransform<N,T> extends Transform { + + /** Represents no transform. */ + public static class NULL extends PojoTransform<Object,Object> {} + + Class<N> normalClass; + Class<T> transformedClass; + ClassMeta<T> transformedClassMeta; + + /** + * Constructor. + */ + @SuppressWarnings("unchecked") + protected PojoTransform() { + super(); + + Class<?> c = this.getClass().getSuperclass(); + Type t = this.getClass().getGenericSuperclass(); + while (c != PojoTransform.class) { + t = c.getGenericSuperclass(); + c = c.getSuperclass(); + } + + // Attempt to determine the T and G classes using reflection. + if (t instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType)t; + Type[] pta = pt.getActualTypeArguments(); + if (pta.length == 2) { + Type nType = pta[0]; + if (nType instanceof Class) { + this.normalClass = (Class<N>)nType; + + // <byte[],x> ends up containing a GenericArrayType, so it has to + // be handled as a special case. + } else if (nType instanceof GenericArrayType) { + Class<?> cmpntType = (Class<?>)((GenericArrayType)nType).getGenericComponentType(); + this.normalClass = (Class<N>)Array.newInstance(cmpntType, 0).getClass(); + + // <Class<?>,x> ends up containing a ParameterizedType, so just use the raw type. + } else if (nType instanceof ParameterizedType) { + this.normalClass = (Class<N>)((ParameterizedType)nType).getRawType(); + + } else + throw new RuntimeException("Unsupported parameter type: " + nType); + if (pta[1] instanceof Class) + this.transformedClass = (Class<T>)pta[1]; + else if (pta[1] instanceof ParameterizedType) + this.transformedClass = (Class<T>)((ParameterizedType)pta[1]).getRawType(); + else + throw new RuntimeException("Unexpected transformed class type: " + pta[1].getClass().getName()); + } + } + } + + /** + * Constructor for when the normal and transformed classes are already known. + * + * @param normalClass The normal class (cannot be serialized). + * @param transformedClass The transformed class (serializable). + */ + protected PojoTransform(Class<N> normalClass, Class<T> transformedClass) { + this.normalClass = normalClass; + this.transformedClass = transformedClass; + } + + /** + * If this transform is to be used to serialize non-serializable POJOs, it must implement this method. + * <p> + * The object must be converted into one of the following serializable types: + * <ul class='spaced-list'> + * <li>{@link String} + * <li>{@link Number} + * <li>{@link Boolean} + * <li>{@link Collection} containing anything on this list. + * <li>{@link Map} containing anything on this list. + * <li>A java bean with properties of anything on this list. + * <li>An array of anything on this list. + * </ul> + * + * @param o The object to be transformed. + * @return The transformed object. + * @throws SerializeException If a problem occurred trying to convert the output. + */ + public T transform(N o) throws SerializeException { + throw new SerializeException("Generalize method not implemented on transform ''{0}''", this.getClass().getName()); + } + + /** + * If this transform is to be used to reconstitute POJOs that aren't true Java beans, it must implement this method. + * + * @param f The transformed object. + * @param hint If possible, the parser will try to tell you the object type being created. For example, + * on a serialized date, this may tell you that the object being created must be of type {@code GregorianCalendar}.<br> + * This may be <jk>null</jk> if the parser cannot make this determination. + * @return The narrowed object. + * @throws ParseException If this method is not implemented. + */ + public N normalize(T f, ClassMeta<?> hint) throws ParseException { + throw new ParseException("Narrow method not implemented on transform ''{0}''", this.getClass().getName()); + } + + /** + * Returns the T class, the normalized form of the class. + * + * @return The normal form of this class. + */ + public Class<N> getNormalClass() { + return normalClass; + } + + /** + * Returns the G class, the generialized form of the class. + * <p> + * Subclasses must override this method if the generialized class is {@code Object}, + * meaning it can produce multiple generialized forms. + * + * @return The transformed form of this class. + */ + public Class<T> getTransformedClass() { + return transformedClass; + } + + /** + * Returns the {@link ClassMeta} of the transformed class type. + * This value is cached for quick lookup. + * + * @return The {@link ClassMeta} of the transformed class type. + */ + public ClassMeta<T> getTransformedClassMeta() { + if (transformedClassMeta == null) + transformedClassMeta = beanContext.getClassMeta(transformedClass); + return transformedClassMeta; + } + + /** + * Checks if the specified object is an instance of the normal class defined on this transform. + * + * @param o The object to check. + * @return <jk>true</jk> if the specified object is a subclass of the normal class defined on this transform. + * <jk>null</jk> always return <jk>false</jk>. + */ + public boolean isNormalObject(Object o) { + if (o == null) + return false; + return ClassUtils.isParentClass(normalClass, o.getClass()); + } + + /** + * Checks if the specified object is an instance of the transformed class defined on this transform. + * + * @param o The object to check. + * @return <jk>true</jk> if the specified object is a subclass of the transformed class defined on this transform. + * <jk>null</jk> always return <jk>false</jk>. + */ + public boolean isTransformedObject(Object o) { + if (o == null) + return false; + return ClassUtils.isParentClass(transformedClass, o.getClass()); + } + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + @Override /* Transform */ + public Class<?> forClass() { + return normalClass; + } + + @Override /* Object */ + public String toString() { + return getClass().getSimpleName() + '<' + getNormalClass().getSimpleName() + "," + getTransformedClass().getSimpleName() + '>'; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/transform/SurrogateTransform.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/transform/SurrogateTransform.java b/juneau-core/src/main/java/org/apache/juneau/transform/SurrogateTransform.java new file mode 100644 index 0000000..2472f4f --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/transform/SurrogateTransform.java @@ -0,0 +1,207 @@ +/*************************************************************************************************************************** + * 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.juneau.transform; + +import java.lang.reflect.*; +import java.util.*; + +import org.apache.juneau.*; +import org.apache.juneau.annotation.*; +import org.apache.juneau.parser.*; +import org.apache.juneau.serializer.*; + + +/** + * Specialized {@link PojoTransform} for surrogate classes. + * <p> + * Surrogate classes are used in place of other classes during serialization. + * For example, you may want to use a surrogate class to change the names or order of bean + * properties on a bean. + * <p> + * The following is an example of a surrogate class change changes a property name: + * <p class='bcode'> + * <jk>public class</jk> SurrogateClass { + * <jk>public</jk> String surrogateField; <jc>// New bean property</jc> + * + * <jk>public</jk> SurrogateClass(NormalClass normalClass) { + * <jk>this</jk>.surrogateField = normalClass.normalField; + * } + * } + * </p> + * <p> + * Optionally, a public static method can be used to untransform a class during parsing: + * <p class='bcode'> + * <jk>public class</jk> SurrogateClass { + * ... + * <jk>public static</jk> NormalClass <jsm>toNormalClass</jsm>(SurrogateClass surrogateClass) { + * <jk>return new</jk> NormalClass(surrogateClass.transformedField); + * } + * } + * </p> + * <p> + * Surrogate classes must conform to the following: + * <ul class='spaced-list'> + * <li>It must have a one or more public constructors that take in a single parameter whose type is the normal types. + * (It is possible to define a class as a surrogate for multiple class types by using multiple constructors with + * different parameter types). + * <li>It optionally can have a public static method that takes in a single parameter whose type is the transformed type + * and returns an instance of the normal type. This is called the untransform method. The method can be called anything. + * <li>If an untransform method is present, the class must also contain a no-arg constructor (so that the transformed class + * can be instantiated by the parser before being converted into the normal class by the untransform method). + * </ul> + * <p> + * Surrogate classes are associated with serializers and parsers using the {@link CoreApi#addTransforms(Class...)} method. + * <p class='bcode'> + * <ja>@Test</ja> + * <jk>public void</jk> test() <jk>throws</jk> Exception { + * JsonSerializer s = <jk>new</jk> JsonSerializer.Simple().addTransforms(Surrogate.<jk>class</jk>); + * JsonParser p = <jk>new</jk> JsonParser().addTransforms(Surrogate.<jk>class</jk>); + * String r; + * Normal n = Normal.<jsm>create</jsm>(); + * + * r = s.serialize(n); + * assertEquals(<js>"{f2:'f1'}"</js>, r); + * + * n = p.parse(r, Normal.<jk>class</jk>); + * assertEquals(<js>"f1"</js>, n.f1); + * } + * + * <jc>// The normal class</jc> + * <jk>public class</jk> Normal { + * <jk>public</jk> String f1; + * + * <jk>public static</jk> Normal <jsm>create</jsm>() { + * Normal n = <jk>new</jk> Normal(); + * n.f1 = <js>"f1"</js>; + * <jk>return</jk> n; + * } + * } + * + * <jc>// The surrogate class</jc> + * <jk>public static class</jk> Surrogate { + * <jk>public</jk> String f2; + * + * <jc>// Surrogate constructor</jc> + * <jk>public</jk> Surrogate(Normal n) { + * f2 = n.f1; + * } + * + * <jc>// Constructor used during parsing (only needed if untransform method specified)</jc> + * <jk>public</jk> Surrogate() {} + * + * <jc>// Untransform method (optional)</jc> + * <jk>public static</jk> Normal <jsm>toNormal</jsm>(Surrogate f) { + * Normal n = <jk>new</jk> Normal(); + * n.f1 = f.f2; + * <jk>return</jk> n; + * } + * } + * </p> + * <p> + * It should be noted that a surrogate class is functionally equivalent to the following {@link PojoTransform} implementation: + * <p class='bcode'> + * <jk>public static class</jk> SurrogateTransform <jk>extends</jk> PojoTransform<Normal,Surrogate> { + * <jk>public</jk> Surrogate transform(Normal n) <jk>throws</jk> SerializeException { + * <jk>return new</jk> Surrogate(n); + * } + * <jk>public</jk> Normal normalize(Surrogate s, ClassMeta<?> hint) <jk>throws</jk> ParseException { + * <jk>return</jk> Surrogate.<jsm>toNormal</jsm>(s); + * } + * } + * </p> + * + * @author James Bognar ([email protected]) + * @param <T> The class type that this transform applies to. + * @param <F> The transformed class type. + */ +public class SurrogateTransform<T,F> extends PojoTransform<T,F> { + + private Constructor<F> constructor; // public F(T t); + private Method untransformMethod; // public static T valueOf(F f); + + /** + * Constructor. + * + * @param forClass The normal class. + * @param constructor The constructor on the surrogate class that takes the normal class as a parameter. + * @param untransformMethod The static method that converts surrogate objects into normal objects. + */ + protected SurrogateTransform(Class<T> forClass, Constructor<F> constructor, Method untransformMethod) { + super(forClass, constructor.getDeclaringClass()); + this.constructor = constructor; + this.untransformMethod = untransformMethod; + } + + /** + * Given the specified surrogate class, return the list of POJO transforms. + * A transform is returned for each public 1-arg constructor found. + * Returns an empty list if no public 1-arg constructors are found. + * + * @param c The surrogate class. + * @return The list of POJO transforms that apply to this class. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static List<SurrogateTransform<?,?>> findTransforms(Class<?> c) { + List<SurrogateTransform<?,?>> l = new LinkedList<SurrogateTransform<?,?>>(); + for (Constructor<?> cc : c.getConstructors()) { + if (cc.getAnnotation(BeanIgnore.class) == null) { + Class<?>[] pt = cc.getParameterTypes(); + + // Only constructors with one parameter. + // Ignore instance class constructors. + if (pt.length == 1 && pt[0] != c.getDeclaringClass()) { + int mod = cc.getModifiers(); + if (Modifier.isPublic(mod)) { // Only public constructors. + + // Find the untransform method if there is one. + Method untransformMethod = null; + for (Method m : c.getMethods()) { + if (pt[0].equals(m.getReturnType())) { + Class<?>[] mpt = m.getParameterTypes(); + if (mpt.length == 1 && mpt[0].equals(c)) { // Only methods with one parameter and where the return type matches this class. + int mod2 = m.getModifiers(); + if (Modifier.isPublic(mod2) && Modifier.isStatic(mod2)) // Only public static methods. + untransformMethod = m; + } + } + } + + l.add(new SurrogateTransform(pt[0], cc, untransformMethod)); + } + } + } + } + return l; + } + + @Override /* PojoTransform */ + public F transform(T o) throws SerializeException { + try { + return constructor.newInstance(o); + } catch (Exception e) { + throw new SerializeException(e); + } + } + + @Override /* PojoTransform */ + @SuppressWarnings("unchecked") + public T normalize(F f, ClassMeta<?> hint) throws ParseException { + if (untransformMethod == null) + throw new ParseException("static valueOf({0}) method not implement on surrogate class ''{1}''", f.getClass().getName(), getNormalClass().getName()); + try { + return (T)untransformMethod.invoke(null, f); + } catch (Exception e) { + throw new ParseException(e); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/transform/Transform.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/transform/Transform.java b/juneau-core/src/main/java/org/apache/juneau/transform/Transform.java new file mode 100644 index 0000000..3fa17f7 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/transform/Transform.java @@ -0,0 +1,138 @@ +/*************************************************************************************************************************** + * 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.juneau.transform; + +import org.apache.juneau.*; + +/** + * Parent class for all bean and POJO transforms. + * + * + * <h6 class='topic'>Description</h6> + * <p> + * Transforms are used to alter how POJOs are handled by bean contexts (and subsequently serializers and parsers). + * The are a very powerful feature of the Juneau framework that allows virtually any POJO to be serialized and parsed. + * For example, they can be used to... + * <ul class='spaced-list'> + * <li>Convert a non-serializable POJO into a serializable POJO during serialization (and optionally vis-versa during parsing). + * <li>Control various aspects of beans, such as what properties are visible, bean subclasses, etc... + * </ul> + * <p> + * There are 2 subclasses of transforms: + * <ul class='spaced-list'> + * <li>{@link PojoTransform} - Non-bean transforms for converting POJOs into serializable equivalents. + * <li>{@link BeanTransform} - Bean transforms for configuring how beans are handled. + * </ul> + * <p> + * Transforms are associated with bean contexts (and serializers/parsers) through the {@link ContextFactory#addToProperty(String,Object)} + * and {@link CoreApi#addTransforms(Class[])} methods. + * + * + * <h6 class='topic'>Additional information</h6> + * See {@link org.apache.juneau.transform} for more information. + * + * + * @author James Bognar ([email protected]) + */ +public class Transform { + + /** Represents no transform. */ + public static class NULL extends Transform {} + + /** The transform subtype */ + public static enum TransformType { + /** PojoTransform */ + POJO, + /** BeanTransform */ + BEAN + } + + /** The class that this transform applies to. */ + protected Class<?> forClass; + + /** The bean context that this transform instance belongs to. */ + protected BeanContext beanContext; + + /** Whether this is a BeanTransform or PojoTransform. */ + protected TransformType type = TransformType.POJO; + + Transform() {} + + Transform(Class<?> forClass) { + this.forClass = forClass; + } + + + /** + * Returns the class that this transform applies to. + * + * @return The class that this transform applies to. + */ + public Class<?> forClass() { + return forClass; + } + + /** + * Returns the implementation class. + * Useful for debugging when calling {@link BeanContext#toString()}. + * + * @return The implementation class of this transform. + */ + public Class<?> getImplClass() { + return this.getClass(); + } + + /** + * Returns whether this is an instance of {@link PojoTransform} or {@link BeanTransform}. + * + * @return The transform type. + */ + public TransformType getType() { + return type; + } + + /** + * Returns the {@link BeanContext} that created this transform. + * + * @return The bean context that created this transform. + */ + protected BeanContext getBeanContext() { + return beanContext; + } + + /** + * Sets the bean context that this transform instance was created by. + * + * @param beanContext The bean context that created this transform. + * @return This object (for method chaining). + */ + public Transform setBeanContext(BeanContext beanContext) { + this.beanContext = beanContext; + return this; + } + + @Override /* Object */ + public int hashCode() { + return getClass().getName().hashCode() + forClass().getName().hashCode(); + } + + /** + * Checks if the specified transform class is the same as this one. + * + * @param f The transform to check. + * @return <jk>true</jk> if the specified transform is equivalent to this one. + */ + public boolean isSameAs(Transform f) { + return f.getClass().equals(getClass()) && f.forClass().equals(forClass()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/transform/doc-files/classes.png ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/transform/doc-files/classes.png b/juneau-core/src/main/java/org/apache/juneau/transform/doc-files/classes.png new file mode 100644 index 0000000..0a2ef0d Binary files /dev/null and b/juneau-core/src/main/java/org/apache/juneau/transform/doc-files/classes.png differ
