Package au.net.causal.projo.prefs.transform

Source Code of au.net.causal.projo.prefs.transform.ListTransformer$ElementReadingEndpoint

package au.net.causal.projo.prefs.transform;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.AbstractList;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.supercsv.io.CsvListReader;
import org.supercsv.io.CsvListWriter;
import org.supercsv.prefs.CsvPreference;

import au.net.causal.projo.bind.MetadataUtils;
import au.net.causal.projo.prefs.DataTypeSupport;
import au.net.causal.projo.prefs.PreferenceKeyMetadata;
import au.net.causal.projo.prefs.PreferencesException;
import au.net.causal.projo.prefs.TransformDataTypeSupportChain;
import au.net.causal.projo.prefs.TransformGetChain;
import au.net.causal.projo.prefs.TransformPutChain;
import au.net.causal.projo.prefs.TransformRemoveChain;
import au.net.causal.projo.prefs.TransformResult;
import au.net.causal.projo.prefs.UnsupportedDataTypeException;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import com.google.common.reflect.TypeToken;

/**
* Converts lists of values to a comma-separated string if the store does not support storing lists natively.
* <p>
*
* Requires that the list component's data type can be converted to string form, or else this transformer will not declare support for the specific
* list data type.
*
* @author prunge
*/
public class ListTransformer implements PreferenceTransformer
{
  private static final Logger log = LoggerFactory.getLogger(ListTransformer.class);
 
  private final CsvPreference csvFormat = new CsvPreference.Builder(
                          (char)CsvPreference.STANDARD_PREFERENCE.getQuoteChar(),
                          CsvPreference.STANDARD_PREFERENCE.getDelimiterChar(),
                          "" /* end of line symbols */).build();
                         
 
  @Override
  public <T> TransformResult<T> applyGet(String key, PreferenceKeyMetadata<T> keyMetadata, TransformGetChain chain) throws PreferencesException
  {
    if (!isSupported(keyMetadata, chain))
      return(null);
   
    TypeToken<?> elementType = collectionElementType(keyMetadata.getDataType());
    PreferenceKeyMetadata<?> elementKeyMetadata = keyMetadata.withDataType(elementType);

    //element type is appropriate, so it safe
    ElementReadingEndpoint endpoint = new ElementReadingEndpoint(chain);
    Collection<Object> col = createAppropriateCollectionType(keyMetadata);
    Object elementValue;
    int index = 0;
    do
    {
      endpoint.setIndex(index);
      elementValue = chain.getValueWithRestartedChain(key, (PreferenceKeyMetadata)elementKeyMetadata, endpoint);
     
      if (elementValue != null)
        col.add(elementValue);
     
      index++;
    }
    while (elementValue != null);
   
    return(new TransformResult<>((T)col));
  }
 
  private Collection<Object> createAppropriateCollectionType(PreferenceKeyMetadata<?> keyMetadata)
  throws PreferencesException
  {
    Class<?> listType = keyMetadata.getDataType().getRawType();
   
    //Default to array list
    if (List.class.equals(listType) || AbstractList.class.equals(listType) || Collection.class.equals(listType))
      return(new ArrayList<>());
   
    //Default to linked hash set for sets
    if (Set.class.equals(listType) || AbstractSet.class.equals(listType))
      return(new LinkedHashSet<>());
   
    //Sorted sets we'll use tree set
    if (SortedSet.class.equals(listType) || NavigableSet.class.equals(listType))
      return(new TreeSet<>());
   
    else if (!listType.isInterface() && !Modifier.isAbstract(listType.getModifiers()))
    {
      //Must be instantiable with zero-arg constructor
      try
      {
        //It's our list we are creating so this is safe
        List<Object> list = (List<Object>)listType.getConstructor().newInstance();
       
        return(list);
      }
      catch (NoSuchMethodException | IllegalAccessException | InstantiationException e)
      {
        throw new PreferencesException("Cannot instantiate list of type " + listType + ".", e);
      }
      catch (InvocationTargetException e)
      {
        if (e.getCause() instanceof RuntimeException)
          throw ((RuntimeException)e.getCause());
        else if (e.getCause() instanceof Error)
          throw ((Error)e.getCause());
        else if (e.getCause() instanceof PreferencesException)
          throw ((PreferencesException)e.getCause());
        else
          throw new PreferencesException("Cannot instantiate list of type " + listType + ".", e.getCause());
      }
    }
    else
      throw new PreferencesException("Cannot instantiate list of type " + listType + " since it cannot be instantiated.");
  }

  @Override
  public <T> boolean applyPut(String key, T value, PreferenceKeyMetadata<T> keyMetadata, TransformPutChain chain) throws PreferencesException
  {
    if (!isSupported(keyMetadata, chain))
      return(false);

    TypeToken<?> elementType = collectionElementType(keyMetadata.getDataType());
    PreferenceKeyMetadata<?> elementKeyMetadata = keyMetadata.withDataType(elementType);
   
    //For each element in the list, create new endpoint, run chain with element to endpoint
    Iterable<?> valueList = (Iterable<?>)value;
    Table<Integer, String, String> valueTable = HashBasedTable.create();
    int index = 0;
    for (Object element : valueList)
    {
      StringStorageEndpoint elementEndpoint = new StringStorageEndpoint(valueTable.row(index));
     
      //element type is appropriate, so it safe
      chain.putValueWithRestartedChain(key, element, (PreferenceKeyMetadata)elementKeyMetadata, elementEndpoint);
     
      index++;
    }
   
    //log.info("Results: " + valueTable);

    //Convert to our format using CSV writer?
    for (String colKey : valueTable.columnKeySet())
    {
      List<String> colValueList = new ArrayList<>();
      for (int i = 0; i < index; i++)
      {
        String curVal = valueTable.get(i, colKey);
        if (curVal == null)
          curVal = "";
         
        //curVal = StringEscapeUtils.escapeCsv(curVal);
       
        colValueList.add(curVal);
      }
     
      StringWriter buf = new StringWriter();
     
      try (CsvListWriter csvWriter = new CsvListWriter(buf, csvFormat))
      {
        csvWriter.write(colValueList);
      }
      catch (IOException e)
      {
        //Should not happen since string writer doesn't throw I/O errors
        throw new PreferencesException(e);
      }
     
      String completeValue = buf.toString();
      chain.putValue(colKey, completeValue, keyMetadata.withDataType(String.class));
      log.debug("Wrote value " + completeValue + " for key " + key);
    }
   
    return(true);
  }

  @Override
  public <T> boolean applyRemove(String key, PreferenceKeyMetadata<T> keyMetadata, TransformRemoveChain chain) throws PreferencesException
  {
    if (!isSupported(keyMetadata, chain))
      return(false);
   
    chain.removeValue(key, keyMetadata.withDataType(String.class));
    return(true);
  }

  private boolean isSupported(PreferenceKeyMetadata<?> keyMetadata, TransformDataTypeSupportChain chain)
  throws PreferencesException
  {
    //Must not be supported natively
    if (chain.isDataTypeSupportedNatively(keyMetadata))
      return(false);
   
    //Must be a collection
    if (!TypeToken.of(Collection.class).isAssignableFrom(keyMetadata.getDataType()))
      return(false);
   
    //The target is a string, so must support writing strings
    if (!chain.isDataTypeSupported(keyMetadata.withDataType(String.class)))
      return(false);
   
    //Must also support converting element type into strings
    if (!chain.isDataTypeSupportedWithRestartedChain(keyMetadata.withDataType(collectionElementType(keyMetadata.getDataType())), new StringDataTypeEndpoint()))
      return(false);
   
    return(true);

  }
 
  @Override
  public DataTypeSupport applyDataTypeSupport(PreferenceKeyMetadata<?> keyMetadata, TransformDataTypeSupportChain chain) throws PreferencesException
  {
    //Must be a collection
    if (!TypeToken.of(Collection.class).isAssignableFrom(keyMetadata.getDataType()))
      return(null);
   
    //The target is a string, so must support writing strings
    if (!chain.isDataTypeSupported(keyMetadata.withDataType(String.class)))
      return(null);
   
    //Must also support converting element type into strings
    if (!chain.isDataTypeSupportedWithRestartedChain(keyMetadata.withDataType(collectionElementType(keyMetadata.getDataType())), new StringDataTypeEndpoint()))
      return(null);
   
    return(DataTypeSupport.ADD_SUPPORT);
  }
 
  private TypeToken<?> collectionElementType(TypeToken<?> collectionType)
  {
    return(TypeToken.of(MetadataUtils.getCollectionOrArrayComponentGenericType(collectionType.getType())));
  }
 
  private class ElementReadingEndpoint extends StringDataTypeEndpoint implements TransformGetChain
  {
    private int index;
    private final TransformGetChain originalChain;
   
    private final Map<String, List<String>> cache = new HashMap<>();
   
    public ElementReadingEndpoint(TransformGetChain originalChain)
    {
      this.originalChain = originalChain;
    }
   
    @Override
    public <T> T getValue(String key, PreferenceKeyMetadata<T> keyMetadata) throws UnsupportedDataTypeException, PreferencesException
    {
      List<String> values = cache.get(key);
      if (values == null)
      {
        String sValue = originalChain.getValue(key, keyMetadata.withDataType(String.class));
        try (CsvListReader reader = new CsvListReader(new StringReader(sValue), csvFormat))
        {
          values = reader.read();
          if (values == null)
            values = Collections.emptyList();
         
          cache.put(key, values);
        }
        catch (IOException e)
        {
          throw new PreferencesException("Error parsing CSV value: " + sValue, e);
        }
      }
     
      if (index < values.size())
        return((T)values.get(index));
      else
        return(null);
    }
   
    @Override
    public <T> T getValueWithRestartedChain(String key, PreferenceKeyMetadata<T> keyMetadata, TransformGetChain newEndpoint)
    throws UnsupportedDataTypeException, PreferencesException
    {
      //It's an endpoint
      return(getValue(key, keyMetadata));
    }
   
    public void setIndex(int index)
    {
      this.index = index;
    }
  }
 
  private static class StringStorageEndpoint extends StringDataTypeEndpoint implements TransformPutChain
  {
    private final Map<? super String, ? super String> valueMap;
 
    public StringStorageEndpoint(Map<? super String, ? super String> valueMap)
    {
      this.valueMap = valueMap;
    }
   
    @Override
    public <T> void putValue(String key, T value, PreferenceKeyMetadata<T> keyMetadata)
    throws UnsupportedDataTypeException, PreferencesException
    {
      if (value != null)
        valueMap.put(key, value.toString());
    }
   
    @Override
    public <T> void putValueWithRestartedChain(String key, T value, PreferenceKeyMetadata<T> keyMetadata, TransformPutChain newEndpoint)
    throws UnsupportedDataTypeException, PreferencesException
    {
      //It's an endpoint
      putValue(key, value, keyMetadata);
    }
  }
 
  private static class StringDataTypeEndpoint implements TransformDataTypeSupportChain
  {
    @Override
    public boolean isDataTypeSupported(PreferenceKeyMetadata<?> keyMetadata) throws PreferencesException
    {
      return(String.class.equals(keyMetadata.getDataType().getRawType()));
    }
   
    @Override
    public boolean isDataTypeSupportedNatively(PreferenceKeyMetadata<?> keyMetadata) throws PreferencesException
    {
      return(isDataTypeSupported(keyMetadata));
    }
   
    @Override
    public boolean isDataTypeSupportedNativelyWithRestartedChain(PreferenceKeyMetadata<?> keyMetadata, TransformDataTypeSupportChain endpoint)
    throws PreferencesException
    {
      //It's an endpoint, not a chain
      return(isDataTypeSupported(keyMetadata));
    }
   
    @Override
    public boolean isDataTypeSupportedWithRestartedChain(PreferenceKeyMetadata<?> keyMetadata, TransformDataTypeSupportChain endpoint)
    throws PreferencesException
    {
      //It's an endpoint, not a chain
      return(isDataTypeSupported(keyMetadata));
    }
  }

}
TOP

Related Classes of au.net.causal.projo.prefs.transform.ListTransformer$ElementReadingEndpoint

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.