All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.damnhandy.uri.template.DefaultVarExploder Maven / Gradle / Ivy

Go to download

Handy URI Templates is a RFC6570 compliant URI template processor. The library allows clients to utilize templatized URIs and inject replacement variables to expand the template into a URI. The library sports a fluent API, ability to plugin custom object renderers, and supports all levels of URI templates.

There is a newer version: 2.1.8
Show newest version
/*
 * Copyright 2012, Ryan J. McDonough
 *
 * Licensed 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 com.damnhandy.uri.template;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;


/**
 * 

* The {@link DefaultVarExploder} is a {@link VarExploder} implementation that takes in a Java object and * extracts the properties for use in a URI Template. Given the following URI expression: *

*
 * /mapper{?address*}
 * 
*

* And this Java object for an address: *

*
 * Address address = new Address();
 * address.setState("CA");
 * address.setCity("Newport Beach");
 * String result = UriTemplate.fromTemplate("/mapper{?address*}").set("address", address).expand();
 * 
*

* The expanded URI will be: *

*
 * /mapper?city=Newport%20Beach&state=CA
 * 
*

*

* The {@link DefaultVarExploder} breaks down the object properties as follows: *

    *
  • All properties that contain a non-null return value will be included
  • *
  • Getters or fields annotated with {@link UriTransient} will NOT included in the list
  • *
  • By default, the property name is used as the label in the URI. This can be overridden by * placing the {@link VarName} annotation on the field or getter method and specifying a name.
  • *
  • Field level annotation take priority of getter annotations
  • *
* * @author Ryan J. McDonough * @version $Revision: 1.1 $ * @see VarName * @see UriTransient * @see VarExploder * @since 1.0 */ public class DefaultVarExploder implements VarExploder { /** * */ private static final String GET_PREFIX = "get"; /** * */ private static final String IS_PREIX = "is"; /** * The original object. */ private Object source; /** * The objects properties that have been extracted to a {@link Map} */ private Map pairs = new TreeMap(); /** * @param source the Object to explode */ public DefaultVarExploder(Object source) throws VarExploderException { this.setSource(source); } /** * @return the name value pairs of the input */ @Override public Map getNameValuePairs() { return pairs; } /** * * @param source * @throws VarExploderException */ void setSource(Object source) throws VarExploderException { this.source = source; this.initValues(); } /** * Initializes the values from the object properties and constructs a * map from those values. * * @throws VarExploderException */ private void initValues() throws VarExploderException { Class c = source.getClass(); if (c.isAnnotation() || c.isArray() || c.isEnum() || c.isPrimitive()) { throw new IllegalArgumentException("The value must an object"); } if(source instanceof Map ) { this.pairs = (Map) source; return; } Method[] methods = c.getMethods(); for (Method method : methods) { inspectGetters(method); } scanFields(c); } /** * A lite version of the introspection logic performed by the BeanInfo introspector. * @param method */ private void inspectGetters(Method method) { String methodName = method.getName(); int prefixLength = 0; if (methodName.startsWith(GET_PREFIX)) { prefixLength = GET_PREFIX.length(); } if (methodName.startsWith(IS_PREIX)) { prefixLength = IS_PREIX.length(); } if(prefixLength == 0) { return; } String name = decapitalize(methodName.substring(prefixLength)); if(!isValidProperty(name)) { return; } // Check that the return type is not null or void Class propertyType = method.getReturnType(); if (propertyType == null || propertyType == void.class) { return; } // isXXX return boolean if (prefixLength == 2) { if (!(propertyType == boolean.class)) { return; } } // validate parameter types Class[] paramTypes = method.getParameterTypes(); if (paramTypes.length > 1 || (paramTypes.length == 1 && paramTypes[0] != int.class)) { return; } if (!method.isAnnotationPresent(UriTransient.class) && !"class".equals(name)) { Object value = getValue(method); if (method.isAnnotationPresent(VarName.class)) { name = method.getAnnotation(VarName.class).value(); } if (value != null) { pairs.put(name, value); } } } private static boolean isValidProperty(String propertyName) { return (propertyName != null) && (propertyName.length() != 0); } static String decapitalize(String name) { if (name == null) return null; // The rule for decapitalize is that: // If the first letter of the string is Upper Case, make it lower case // UNLESS the second letter of the string is also Upper Case, in which case no // changes are made. if (name.length() == 0 || (name.length() > 1 && Character.isUpperCase(name.charAt(1)))) { return name; } char[] chars = name.toCharArray(); chars[0] = Character.toLowerCase(chars[0]); return new String(chars); } /** * Scans the fields on the class or super classes to look for * field-level annotations. * * @param c */ private void scanFields(Class c) { if (!c.isInterface()) { Field[] fields = c.getDeclaredFields(); for (Field field : fields) { String fieldName = field.getName(); if (pairs.containsKey(fieldName)) { if (field.isAnnotationPresent(UriTransient.class)) { pairs.remove(fieldName); } else if (field.isAnnotationPresent(VarName.class)) { String name = field.getAnnotation(VarName.class).value(); pairs.put(name, pairs.get(fieldName)); pairs.remove(fieldName); } } } } /* * We still need to scan the fields of the super class if its * not Object to check for annotations. There might be a better * way to do this. */ if (!c.getSuperclass().equals(Object.class)) { scanFields(c.getSuperclass()); } } /** * Return the value of the property. * * @param method * @return * @throws VarExploderException */ private Object getValue(Method method) throws VarExploderException { try { if (method == null) { return null; } return method.invoke(source); } catch (IllegalArgumentException e) { throw new VarExploderException(e); } catch (IllegalAccessException e) { throw new VarExploderException(e); } catch (InvocationTargetException e) { throw new VarExploderException(e); } } @Override public Collection getValues() throws VarExploderException { return pairs.values(); } }