| /* |
| * 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.qi4j.api.composite; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.lang.reflect.Array; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.ParameterizedType; |
| import java.lang.reflect.Type; |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| import org.qi4j.api.Qi4j; |
| import org.qi4j.api.property.GenericPropertyInfo; |
| import org.qi4j.api.property.Property; |
| import org.qi4j.api.util.Classes; |
| import org.qi4j.api.util.Dates; |
| import org.qi4j.api.value.ValueComposite; |
| |
| /** |
| * Transfer java.util.Properties to Composite properties |
| */ |
| public final class PropertyMapper |
| { |
| |
| private final static Map<Type, MappingStrategy> STRATEGY; |
| |
| static |
| { |
| STRATEGY = new HashMap<>(); |
| STRATEGY.put( Integer.class, new IntegerMapper() ); |
| STRATEGY.put( Long.class, new LongMapper() ); |
| STRATEGY.put( Short.class, new ShortMapper() ); |
| STRATEGY.put( Byte.class, new ByteMapper() ); |
| STRATEGY.put( String.class, new StringMapper() ); |
| STRATEGY.put( Character.class, new CharMapper() ); |
| STRATEGY.put( Float.class, new FloatMapper() ); |
| STRATEGY.put( Double.class, new DoubleMapper() ); |
| STRATEGY.put( Date.class, new DateMapper() ); |
| STRATEGY.put( Boolean.class, new BooleanMapper() ); |
| STRATEGY.put( BigDecimal.class, new BigDecimalMapper() ); |
| STRATEGY.put( BigInteger.class, new BigIntegerMapper() ); |
| STRATEGY.put( Enum.class, new EnumMapper() ); |
| STRATEGY.put( Array.class, new ArrayMapper() ); |
| STRATEGY.put( Map.class, new MapMapper() ); |
| STRATEGY.put( List.class, new ListMapper() ); |
| STRATEGY.put( Set.class, new SetMapper() ); |
| STRATEGY.put( ValueComposite.class, new ValueCompositeMapper() ); |
| } |
| |
| /** |
| * Populate the Composite with properties from the given properties object. |
| * |
| * @param props properties object |
| * @param composite the composite instance |
| * |
| * @throws IllegalArgumentException if properties could not be transferred to composite |
| */ |
| public static void map( Properties props, Composite composite ) |
| throws IllegalArgumentException |
| { |
| for( Map.Entry<Object, Object> objectObjectEntry : props.entrySet() ) |
| { |
| try |
| { |
| String methodName = objectObjectEntry.getKey().toString(); |
| Method propertyMethod = composite.getClass().getInterfaces()[ 0 ].getMethod( methodName ); |
| propertyMethod.setAccessible( true ); |
| Object value = objectObjectEntry.getValue(); |
| Type propertyType = GenericPropertyInfo.propertyTypeOf( propertyMethod ); |
| |
| value = mapToType( composite, propertyType, value.toString() ); |
| |
| @SuppressWarnings( "unchecked" ) |
| Property<Object> property = (Property<Object>) propertyMethod.invoke( composite ); |
| property.set( value ); |
| } |
| catch( NoSuchMethodException e ) |
| { |
| throw new IllegalArgumentException( "Could not find any property named " + objectObjectEntry.getKey() ); |
| } |
| catch( IllegalAccessException e ) |
| { |
| //noinspection ThrowableInstanceNeverThrown |
| throw new IllegalArgumentException( "Could not populate property named " + objectObjectEntry.getKey(), e ); |
| } |
| catch( InvocationTargetException e ) |
| { |
| //noinspection ThrowableInstanceNeverThrown |
| String message = "Could not populate property named " + objectObjectEntry.getKey(); |
| throw new IllegalArgumentException( message, e ); |
| } |
| } |
| } |
| |
| @SuppressWarnings( "raw" ) |
| private static Object mapToType( Composite composite, Type propertyType, Object value ) |
| { |
| final String stringValue = value.toString(); |
| MappingStrategy strategy; |
| if( propertyType instanceof Class ) |
| { |
| Class type = (Class) propertyType; |
| if( type.isArray() ) |
| { |
| strategy = STRATEGY.get( Array.class ); |
| } |
| else if( Enum.class.isAssignableFrom( Classes.RAW_CLASS.map( propertyType ) ) ) |
| { |
| strategy = STRATEGY.get( Enum.class ); |
| } |
| else |
| { |
| strategy = STRATEGY.get( type ); |
| } |
| if( strategy == null ) // If null, try with the ValueComposite Mapper... |
| { |
| strategy = STRATEGY.get( ValueComposite.class ); |
| } |
| } |
| else if( propertyType instanceof ParameterizedType ) |
| { |
| ParameterizedType type = ( (ParameterizedType) propertyType ); |
| |
| if( type.getRawType() instanceof Class ) |
| { |
| Class clazz = (Class) type.getRawType(); |
| if( List.class.isAssignableFrom( clazz ) ) |
| { |
| strategy = STRATEGY.get( List.class ); |
| } |
| else if( Set.class.isAssignableFrom( clazz ) ) |
| { |
| strategy = STRATEGY.get( Set.class ); |
| } |
| else if( Map.class.isAssignableFrom( clazz ) ) |
| { |
| strategy = STRATEGY.get( Map.class ); |
| } |
| else |
| { |
| throw new IllegalArgumentException( propertyType + " is not supported." ); |
| } |
| } |
| else |
| { |
| throw new IllegalArgumentException( propertyType + " is not supported." ); |
| } |
| } |
| else |
| { |
| throw new IllegalArgumentException( propertyType + " is not supported." ); |
| } |
| |
| if( strategy == null ) |
| { |
| throw new IllegalArgumentException( propertyType + " is not supported." ); |
| } |
| |
| return strategy.map( composite, propertyType, stringValue ); |
| } |
| |
| /** |
| * Load a Properties object from the given stream, close it, and then populate |
| * the Composite with the properties. |
| * |
| * @param propertyInputStream properties input stream |
| * @param composite the instance |
| * |
| * @throws IOException if the stream could not be read |
| */ |
| |
| public static void map( InputStream propertyInputStream, Composite composite ) |
| throws IOException |
| { |
| if( propertyInputStream != null ) |
| { |
| Properties configProps = new Properties(); |
| try |
| { |
| configProps.load( propertyInputStream ); |
| } |
| finally |
| { |
| propertyInputStream.close(); |
| } |
| map( configProps, composite ); |
| } |
| } |
| |
| /** |
| * Create Properties object which is backed by the given Composite. |
| * |
| * @param composite the instance |
| * |
| * @return properties instance |
| */ |
| public static Properties toJavaProperties( final Composite composite ) |
| { |
| return new Properties() |
| { |
| private static final long serialVersionUID = 3550125427530538865L; |
| |
| @Override |
| public Object get( Object o ) |
| { |
| try |
| { |
| Method propertyMethod = composite.getClass().getMethod( o.toString() ); |
| Property<?> property = (Property<?>) propertyMethod.invoke( composite ); |
| return property.get(); |
| } |
| catch( NoSuchMethodException | IllegalAccessException | InvocationTargetException e ) |
| { |
| return null; |
| } |
| } |
| |
| @Override |
| public Object put( Object o, Object o1 ) |
| { |
| Object oldValue = get( o ); |
| |
| try |
| { |
| Method propertyMethod = composite.getClass().getMethod( o.toString(), Object.class ); |
| propertyMethod.invoke( composite, o1 ); |
| } |
| catch( NoSuchMethodException | IllegalAccessException | InvocationTargetException e ) |
| { |
| e.printStackTrace(); |
| } |
| |
| return oldValue; |
| } |
| }; |
| } |
| |
| private static void tokenize( String valueString, boolean mapSyntax, TokenizerCallback callback ) |
| { |
| char[] data = valueString.toCharArray(); |
| |
| int oldPos = 0; |
| for( int pos = 0; pos < data.length; pos++ ) |
| { |
| char ch = data[ pos ]; |
| if( ch == '\"' ) |
| { |
| pos = resolveQuotes( valueString, callback, data, pos, '\"' ); |
| oldPos = pos; |
| } |
| if( ch == '\'' ) |
| { |
| pos = resolveQuotes( valueString, callback, data, pos, '\'' ); |
| oldPos = pos; |
| } |
| if( ch == ',' || ( mapSyntax && ch == ':' ) ) |
| { |
| String token = new String( data, oldPos, pos - oldPos ); |
| callback.token( token ); |
| oldPos = pos + 1; |
| } |
| } |
| String token = new String( data, oldPos, data.length - oldPos ); |
| callback.token( token ); |
| } |
| |
| private static int resolveQuotes( String valueString, |
| TokenizerCallback callback, |
| char[] data, |
| int pos, char quote |
| ) |
| { |
| boolean found = false; |
| for( int j = pos + 1; j < data.length; j++ ) |
| { |
| if( !found ) |
| { |
| if( data[ j ] == quote ) |
| { |
| String token = new String( data, pos + 1, j - pos - 1 ); |
| callback.token( token ); |
| found = true; |
| } |
| } |
| else |
| { |
| if( data[ j ] == ',' ) |
| { |
| return j + 1; |
| } |
| } |
| } |
| if( !found ) |
| { |
| throw new IllegalArgumentException( "String is not quoted correctly: " + valueString ); |
| } |
| return data.length; |
| } |
| |
| private interface TokenizerCallback |
| { |
| void token( String token ); |
| } |
| |
| private interface MappingStrategy |
| { |
| Object map( Composite composite, Type type, String value ); |
| } |
| |
| private static class StringMapper |
| implements MappingStrategy |
| { |
| @Override |
| public Object map( Composite composite, Type type, String value ) |
| { |
| return value; |
| } |
| } |
| |
| private static class IntegerMapper |
| implements MappingStrategy |
| { |
| @Override |
| public Object map( Composite composite, Type type, String value ) |
| { |
| return new Integer( value.trim() ); |
| } |
| } |
| |
| private static class FloatMapper |
| implements MappingStrategy |
| { |
| @Override |
| public Object map( Composite composite, Type type, String value ) |
| { |
| return new Float( value.trim() ); |
| } |
| } |
| |
| private static class DoubleMapper |
| implements MappingStrategy |
| { |
| @Override |
| public Object map( Composite composite, Type type, String value ) |
| { |
| return new Double( value.trim() ); |
| } |
| } |
| |
| private static class LongMapper |
| implements MappingStrategy |
| { |
| @Override |
| public Object map( Composite composite, Type type, String value ) |
| { |
| return new Long( value.trim() ); |
| } |
| } |
| |
| private static class ShortMapper |
| implements MappingStrategy |
| { |
| @Override |
| public Object map( Composite composite, Type type, String value ) |
| { |
| return new Short( value.trim() ); |
| } |
| } |
| |
| private static class ByteMapper |
| implements MappingStrategy |
| { |
| @Override |
| public Object map( Composite composite, Type type, String value ) |
| { |
| return new Byte( value.trim() ); |
| } |
| } |
| |
| private static class CharMapper |
| implements MappingStrategy |
| { |
| @Override |
| public Object map( Composite composite, Type type, String value ) |
| { |
| return value.trim().charAt( 0 ); |
| } |
| } |
| |
| private static class BigDecimalMapper |
| implements MappingStrategy |
| { |
| @Override |
| public Object map( Composite composite, Type type, String value ) |
| { |
| return new BigDecimal( value.trim() ); |
| } |
| } |
| |
| private static class BigIntegerMapper |
| implements MappingStrategy |
| { |
| @Override |
| public Object map( Composite composite, Type type, String value ) |
| { |
| return new BigInteger( value.trim() ); |
| } |
| } |
| |
| private static class EnumMapper |
| implements MappingStrategy |
| { |
| @Override |
| @SuppressWarnings( "unchecked" ) |
| public Object map( Composite composite, Type type, String value ) |
| { |
| return Enum.valueOf( (Class<Enum>) type, value ); |
| } |
| } |
| |
| private static class DateMapper |
| implements MappingStrategy |
| { |
| @Override |
| public Object map( Composite composite, Type type, String value ) |
| { |
| return Dates.fromString( value.trim() ); |
| } |
| } |
| |
| private static class ValueCompositeMapper |
| implements MappingStrategy |
| { |
| @Override |
| @SuppressWarnings( "unchecked" ) |
| public Object map( Composite composite, Type type, String value ) |
| { |
| return Qi4j.FUNCTION_COMPOSITE_INSTANCE_OF.map( composite ).module().newValueFromSerializedState( (Class<Object>) type, value ); |
| } |
| } |
| |
| private static class ArrayMapper |
| implements MappingStrategy |
| { |
| @Override |
| @SuppressWarnings( {"raw", "unchecked"} ) |
| public Object map( final Composite composite, Type type, String value ) |
| { |
| final Class arrayType = ( (Class) type ).getComponentType(); |
| final ArrayList result = new ArrayList(); |
| tokenize( value, false, new TokenizerCallback() |
| { |
| @Override |
| public void token( String token ) |
| { |
| result.add( mapToType( composite, arrayType, token ) ); |
| } |
| } ); |
| return result.toArray( (Object[]) Array.newInstance( arrayType, result.size() ) ); |
| } |
| } |
| |
| private static class BooleanMapper |
| implements MappingStrategy |
| { |
| @Override |
| public Object map( final Composite composite, Type type, String value ) |
| { |
| return Boolean.valueOf( value.trim() ); |
| } |
| } |
| |
| private static class ListMapper |
| implements MappingStrategy |
| { |
| @Override |
| @SuppressWarnings( {"raw", "unchecked"} ) |
| public Object map( final Composite composite, Type type, String value ) |
| { |
| final Type dataType = ( (ParameterizedType) type ).getActualTypeArguments()[ 0 ]; |
| final Collection result = new ArrayList(); |
| tokenize( value, false, new TokenizerCallback() |
| { |
| @Override |
| public void token( String token ) |
| { |
| result.add( mapToType( composite, dataType, token ) ); |
| } |
| } ); |
| return result; |
| } |
| } |
| |
| private static class SetMapper |
| implements MappingStrategy |
| { |
| @Override |
| @SuppressWarnings( {"raw", "unchecked"} ) |
| public Object map( final Composite composite, Type type, String value ) |
| { |
| final Type dataType = ( (ParameterizedType) type ).getActualTypeArguments()[ 0 ]; |
| final Collection result = new HashSet(); |
| tokenize( value, false, new TokenizerCallback() |
| { |
| @Override |
| public void token( String token ) |
| { |
| result.add( mapToType( composite, dataType, token ) ); |
| } |
| } ); |
| return result; |
| } |
| } |
| |
| private static class MapMapper |
| implements MappingStrategy |
| { |
| @Override |
| @SuppressWarnings( {"raw", "unchecked"} ) |
| public Object map( final Composite composite, Type generictype, String value ) |
| { |
| ParameterizedType type = (ParameterizedType) generictype; |
| final Type keyType = type.getActualTypeArguments()[ 0 ]; |
| final Type valueType = type.getActualTypeArguments()[ 0 ]; |
| final Map result = new HashMap(); |
| tokenize( value, true, new TokenizerCallback() |
| { |
| boolean keyArrivingNext = true; |
| String key; |
| |
| @Override |
| public void token( String token ) |
| { |
| if( keyArrivingNext ) |
| { |
| key = token; |
| keyArrivingNext = false; |
| } |
| else |
| { |
| result.put( mapToType( composite, keyType, key ), mapToType( composite, valueType, token ) ); |
| keyArrivingNext = true; |
| } |
| } |
| } ); |
| return result; |
| } |
| } |
| |
| private PropertyMapper() |
| { |
| } |
| } |