content: package edu.berkeley.cs.amplab.carat.android.storage;
  
  import java.io.UnsupportedEncodingException;
  import java.net.URLDecoder;
  import java.net.URLEncoder;
  import java.util.HashMap;
  import java.util.LinkedList;
  import java.util.List;
  
  import org.apache.thrift.meta_data.FieldMetaData;
  
  import android.util.Log;
  import edu.berkeley.cs.amplab.carat.thrift.BatteryDetails;
  import edu.berkeley.cs.amplab.carat.thrift.CpuStatus;
  import edu.berkeley.cs.amplab.carat.thrift.Feature;
  import edu.berkeley.cs.amplab.carat.thrift.NetworkDetails;
  import edu.berkeley.cs.amplab.carat.thrift.ProcessInfo;
  import edu.berkeley.cs.amplab.carat.thrift.Sample;
  import edu.berkeley.cs.amplab.carat.thrift.Sample._Fields;
  
  /**
   * 
   * This class reads HashMaps from the database and outputs a Sample with fields
   * filled in.
   * 
   * TODO: FIXME: This class needs to be modified when the protocol is changed. Otherwise new fields will not be included in Samples.
   * 
   * @author Eemil Lagerspetz
   * 
   */
  public class SampleReader {
  	
  	public static final String TAG = "SampleReader"; 
  
      /**
       * Library class, instantiation prohibited.
       */
      private SampleReader() {
      }
      
      /**
       * For simplicity, this method relies on the fact that
       * only the piList of Sample has any List type elements.
       * This will fail to record those from substructs (NetworkDetails, BatteryDetails, CpuStatus),
       * and will need to be changed if those are added.
       * 
       * Does not record CallInfo, CellInfo, or CallMonth types.
       */
      public static final HashMap<String, String> writeSample(Sample s) {
          HashMap<String, String> m = new HashMap<String, String>();
          for (_Fields sf: Sample.metaDataMap.keySet()){
              FieldMetaData md = Sample.metaDataMap.get(sf);
              switch (md.valueMetaData.type) {
              case org.apache.thrift.protocol.TType.STRING:
                  m.put(sf.getFieldName(), cleanStr(s.getFieldValue(sf).toString()));
                  break;
              case org.apache.thrift.protocol.TType.I32:
              case org.apache.thrift.protocol.TType.DOUBLE:
                  m.put(sf.getFieldName(), s.getFieldValue(sf).toString());
                  break;
              case org.apache.thrift.protocol.TType.STRUCT:
                  if (md.fieldName.equals(Sample._Fields.NETWORK_DETAILS.getFieldName()) && s.networkDetails != null) {
                      int len = NetworkDetails._Fields.values().length;
                      StringBuilder b = new StringBuilder();
                      for (int i = 1; i <= len; i++){
                          b.append(cleanStr(""+s.networkDetails.getFieldValue(NetworkDetails._Fields.findByThriftId(i))));
                          if ( i < len)
                              b.append("\n");
                      }
                      m.put(sf.getFieldName(), b.toString());
                  } else if (md.fieldName.equals(Sample._Fields.BATTERY_DETAILS.getFieldName()) && s.batteryDetails != null) {
                      int len = BatteryDetails._Fields.values().length;
                      StringBuilder b = new StringBuilder();
                      for (int i = 1; i <= len; i++){
                          b.append(cleanStr(""+s.batteryDetails.getFieldValue(BatteryDetails._Fields.findByThriftId(i))));
                          if ( i < len)
                              b.append("\n");
                      }
                      m.put(sf.getFieldName(), b.toString());
                  } else if (md.fieldName.equals(Sample._Fields.CPU_STATUS.getFieldName()) && s.cpuStatus != null) {
                      int len = CpuStatus._Fields.values().length;
                      StringBuilder b = new StringBuilder();
                      for (int i = 1; i <= len; i++){
                          b.append(cleanStr(""+s.cpuStatus.getFieldValue(CpuStatus._Fields.findByThriftId(i))));
                          if ( i < len)
                              b.append("\n");
                      }
                      m.put(sf.getFieldName(), b.toString());
                  }/*
                    * else if (md.fieldName.equals("CallInfo")){ }
                    */
                  break;
              case org.apache.thrift.protocol.TType.LIST:
                  if (md.fieldName.equals(Sample._Fields.EXTRA.getFieldName()) && s.extra != null) {
                      StringBuilder b = new StringBuilder();
                      for (Feature f : s.extra) {
                          b.append(cleanStr(f.key) + ";" + cleanStr(f.value) + "\n");
                      }
                      if (b.length() > 1)
                      	b.deleteCharAt(b.lastIndexOf("\n"));
                      m.put(sf.getFieldName(), b.toString());
                  } else if (md.fieldName.equals(Sample._Fields.LOCATION_PROVIDERS.getFieldName()) && s.locationProviders != null) {
                      StringBuilder b = new StringBuilder();
                      for (String lp : s.locationProviders)
                          b.append(lp + "\n");
                      if (b.length() > 1)
                      	b.deleteCharAt(b.lastIndexOf("\n"));
                      m.put(sf.getFieldName(), b.toString());
                  } else if (md.fieldName.equals(Sample._Fields.PI_LIST.getFieldName()) && s.piList != null) {
                      StringBuilder b = new StringBuilder();
                      for (ProcessInfo pi : s.piList) {
                          int len = ProcessInfo._Fields.values().length;
                          for (int i = 1; i <= len; i++) {
                              ProcessInfo._Fields pif = ProcessInfo._Fields
                                      .findByThriftId(i);
                              FieldMetaData pmd = ProcessInfo.metaDataMap
                                      .get(pif);
                              if (pmd.valueMetaData.type == org.apache.thrift.protocol.TType.LIST) {
                                  if (pi.appSignatures != null) {
                                      for (int j = 0; j < pi.appSignatures.size(); j++) {
                                          String sig = pi.appSignatures.get(j);
                                          b.append(sig);
                                          if (j + 1 < len)
                                              b.append("#");
                                      }
                                  }
                              } else {
                                  b.append(cleanStr("" + pi.getFieldValue(pif)));
                              }
                              if (i < len)
                                  b.append(";");
                          }
                          b.append("\n");
                      }
                      if (b.length() > 1)
                      	b.deleteCharAt(b.lastIndexOf("\n"));
                      m.put(sf.getFieldName(), b.toString());
                  }
                  break;
              default:
              }
          }
          return m;
      }
  
      /**
       * Read a Sample from a HashMap stored in the Carat Sample db.
       * @param data
       * @return
       */
      public static final Sample readSample(Object data) {
          Sample s = null;
          if (data != null && data instanceof HashMap<?, ?>) {
              HashMap<String, String> m = (HashMap<String, String>) data;
              s = new Sample();
              NetworkDetails n = new NetworkDetails();
              BatteryDetails bd = new BatteryDetails();
              // CellInfo ci = new CellInfo();
              // CallInfo calli = new CallInfo();
              // CallMonth cm = new CallMonth();
              CpuStatus cs = new CpuStatus();
              // Set single fields automatically:
              for (String k : m.keySet()) {
                  _Fields sf = Sample._Fields.findByName(k);
  
                  if (sf != null) {
                      // Top level Sample field.
                      FieldMetaData md = Sample.metaDataMap.get(sf);
                      String cleaned = origStr(m.get(k));
                      switch (md.valueMetaData.type) {
                      case org.apache.thrift.protocol.TType.STRING:
                          s.setFieldValue(sf, cleaned);
                          break;
                      case org.apache.thrift.protocol.TType.I32:
  						try {
  							s.setFieldValue(sf, Integer.parseInt(cleaned));
  						} catch (NumberFormatException e) {
  							Log.e(TAG, "Could not read " + md.fieldName
  									+ ": \"" + cleaned + "\" as an int");
  						}
  						break;
  					case org.apache.thrift.protocol.TType.DOUBLE:
  						try {
  							s.setFieldValue(sf, Double.parseDouble(cleaned));
  						} catch (NumberFormatException e) {
  							Log.e(TAG, "Could not read " + md.fieldName
  									+ ": \"" + cleaned + "\" as a double");
  						}
                          break;
                      case org.apache.thrift.protocol.TType.STRUCT:
                          if (md.fieldName.equals(Sample._Fields.NETWORK_DETAILS.getFieldName())) {
                              fillNetworkDetails(m.get(k), n);
                              s.setNetworkDetails(n);
                          } else if (md.fieldName.equals(Sample._Fields.BATTERY_DETAILS.getFieldName())) {
                              fillBatteryDetails(m.get(k), bd);
                              s.setBatteryDetails(bd);
                          } else if (md.fieldName.equals(Sample._Fields.CPU_STATUS.getFieldName())) {
                              fillCpuStatusDetails(m.get(k), cs);
                              s.setCpuStatus(cs);
                          }/*
                            * else if (md.fieldName.equals("CallInfo")){ }
                            */
                          break;
                      case org.apache.thrift.protocol.TType.LIST:
                          if (md.fieldName.equals(Sample._Fields.EXTRA.getFieldName())) {
                              List<Feature> list = new LinkedList<Feature>();
                              String[] extras = m.get(k).split("\n");
                              for (String e : extras) {
                                  Feature f = new Feature();
                                  String[] feat = e.split(";");
                                  if (feat.length > 1) {
                                      f.setKey(origStr(feat[0]));
                                      f.setValue(origStr(feat[1]));
                                  }
                                  list.add(f);
                              }
                              s.setExtra(list);
                          } else if (md.fieldName.equals(Sample._Fields.LOCATION_PROVIDERS.getFieldName())) {
                              List<String> list = new LinkedList<String>();
                              String[] arr = m.get(k).split("\n");
                              for (String lp : arr)
                                  list.add(lp);
                              s.setLocationProviders(list);
                          }else if (md.fieldName.equals(Sample._Fields.PI_LIST.getFieldName())){
                              // Set piList fields automatically:
                              LinkedList<ProcessInfo> piList = new LinkedList<ProcessInfo>();
                              String[] processes = m.get(md.fieldName).split("\n");
                              for (String process : processes) {
                                  String[] items = process.split(";");
                                  ProcessInfo pi = new ProcessInfo();
                                  /*
                                   * Items are in the same order as they appear in ProcessInfo
                                   * protocol class, so I can use Thrift ID for setting the fields
                                   * automatically.
                                   */
                                  for (int i = 1; i <= items.length; i++) {
                                  	if (items[i-1] == null)
                                  		continue;
                                      ProcessInfo._Fields pif = ProcessInfo._Fields
                                              .findByThriftId(i);
                                      FieldMetaData pmd = ProcessInfo.metaDataMap.get(pif);
                                      cleaned = origStr(items[i - 1]);
                                      switch (pmd.valueMetaData.type) {
                                      case org.apache.thrift.protocol.TType.STRING:
                                          pi.setFieldValue(pif, cleaned);
                                          break;
                                      case org.apache.thrift.protocol.TType.I32:
                                      	try{
                                          pi.setFieldValue(pif, Integer.parseInt(cleaned));
                                      	}catch (NumberFormatException e){
                                      		Log.e(TAG, "Could not read "+md.fieldName+": \""+cleaned+"\" as an int");
                                      	}
                                          break;
                                      case org.apache.thrift.protocol.TType.DOUBLE:
                                      	try{
                                          pi.setFieldValue(pif, Double.parseDouble(cleaned));
                                      	}catch (NumberFormatException e){
                                      		Log.e(TAG, "Could not read "+md.fieldName+": \""+cleaned+"\" as a double");
                                      	}
                                          break;
                                      case org.apache.thrift.protocol.TType.BOOL:
                                      	try{
                                          pi.setFieldValue(pif,
                                                  Boolean.parseBoolean(cleaned));
                                      	}catch (NumberFormatException e){
                                      		Log.e(TAG, "Could not read "+md.fieldName+": \""+cleaned+"\" as a bool");
                                      	}
                                          break;
                                      case org.apache.thrift.protocol.TType.LIST:
                                          List<String> list = new LinkedList<String>();
                                          String[] arr = cleaned.split("#");
                                          for (String sig : arr)
                                              list.add(sig);
                                          pi.setFieldValue(pif, list);
                                          break;
                                      default:
                                      }
                                  }
                                  piList.add(pi);
                              }
                              s.setPiList(piList);
                          }
                          break;
                      default:
                      }
                  }
              }
          }
  
          return s;
      }
  
      private static void fillCpuStatusDetails(String string, CpuStatus cs) {
          String[] items = string.split("\n");
          for (int i = 1; i <= items.length; i++) {
              CpuStatus._Fields pif = CpuStatus._Fields.findByThriftId(i);
              FieldMetaData md = CpuStatus.metaDataMap.get(pif);
              String cleaned = origStr(items[i - 1]);
              switch (md.valueMetaData.type) {
              case org.apache.thrift.protocol.TType.STRING:
                  cs.setFieldValue(pif, cleaned);
                  break;
              case org.apache.thrift.protocol.TType.I32:
              	try{
                  cs.setFieldValue(pif, Integer.parseInt(cleaned));
              }catch (NumberFormatException e){
          		Log.e(TAG, "Could not read "+md.fieldName+": \""+cleaned+"\" as an int");
          	}
                  break;
              case org.apache.thrift.protocol.TType.DOUBLE:
              	try{
                  cs.setFieldValue(pif, Double.parseDouble(cleaned));
              }catch (NumberFormatException e){
          		Log.e(TAG, "Could not read "+md.fieldName+": \""+cleaned+"\" as a double");
          	}
                  break;
              case org.apache.thrift.protocol.TType.BOOL:
              	try{
                  cs.setFieldValue(pif, Boolean.parseBoolean(cleaned));
              	}catch (NumberFormatException e){
              		Log.e(TAG, "Could not read "+md.fieldName+": \""+cleaned+"\" as a bool");
              	}
                  break;
              case org.apache.thrift.protocol.TType.LIST:
                  List<String> list = new LinkedList<String>();
                  String[] arr = items[i - 1].split(";");
                  for (String sig : arr)
                      list.add(sig);
                  cs.setFieldValue(pif, list);
                  break;
              default:
              }
          }
      }
  
  	private static void fillBatteryDetails(String string, BatteryDetails bd) {
  		String[] items = string.split("\n");
  		for (int i = 1; i <= items.length; i++) {
  			BatteryDetails._Fields pif = BatteryDetails._Fields
  					.findByThriftId(i);
  			FieldMetaData md = BatteryDetails.metaDataMap.get(pif);
  			String cleaned = origStr(items[i - 1]);
  			switch (md.valueMetaData.type) {
  			case org.apache.thrift.protocol.TType.STRING:
  				bd.setFieldValue(pif, cleaned);
  				break;
  			case org.apache.thrift.protocol.TType.I32:
  				try {
  					bd.setFieldValue(pif, Integer.parseInt(cleaned));
  				} catch (NumberFormatException e) {
  					Log.e(TAG, "Could not read "+md.fieldName+": \"" + cleaned + "\" as an int");
  				}
  				break;
  			case org.apache.thrift.protocol.TType.DOUBLE:
  				try {
  					bd.setFieldValue(pif, Double.parseDouble(cleaned));
  				} catch (NumberFormatException e) {
  					Log.e(TAG, "Could not read "+md.fieldName+": \"" + cleaned + "\" as a double");
  				}
  				break;
  			case org.apache.thrift.protocol.TType.BOOL:
  				try {
  					bd.setFieldValue(pif, Boolean.parseBoolean(cleaned));
  				} catch (NumberFormatException e) {
  					Log.e(TAG, "Could not read "+md.fieldName+": \"" + cleaned + "\" as a bool");
  				}
  				break;
  			case org.apache.thrift.protocol.TType.LIST:
  				List<String> list = new LinkedList<String>();
  				String[] arr = items[i - 1].split(";");
  				for (String sig : arr)
  					list.add(sig);
  				bd.setFieldValue(pif, list);
  				break;
  			default:
  			}
  		}
  	}
  
  	private static void fillNetworkDetails(String string, NetworkDetails nd) {
  		String[] items = string.split("\n");
  		for (int i = 1; i <= items.length; i++) {
  			NetworkDetails._Fields pif = NetworkDetails._Fields
  					.findByThriftId(i);
  			FieldMetaData md = NetworkDetails.metaDataMap.get(pif);
  			String cleaned = origStr(items[i - 1]);
  			switch (md.valueMetaData.type) {
  			case org.apache.thrift.protocol.TType.STRING:
  				nd.setFieldValue(pif, cleaned);
  				break;
  			case org.apache.thrift.protocol.TType.I32:
  				try {
  					nd.setFieldValue(pif, Integer.parseInt(cleaned));
  				} catch (NumberFormatException e) {
  					Log.e(TAG, "Could not read " + md.fieldName + ": \""
  							+ cleaned + "\" as an int");
  				}
  				break;
  			case org.apache.thrift.protocol.TType.DOUBLE:
  				try {
  					nd.setFieldValue(pif, Double.parseDouble(cleaned));
  				} catch (NumberFormatException e) {
  					Log.e(TAG, "Could not read " + md.fieldName + ": \""
  							+ cleaned + "\" as a double");
  				}
  				break;
  			case org.apache.thrift.protocol.TType.BOOL:
  				try {
  					nd.setFieldValue(pif, Boolean.parseBoolean(cleaned));
  				} catch (NumberFormatException e) {
  					Log.e(TAG, "Could not read " + md.fieldName + ": \""
  							+ cleaned + "\" as a bool");
  				}
  				break;
  			case org.apache.thrift.protocol.TType.LIST:
  				List<String> list = new LinkedList<String>();
  				String[] arr = items[i - 1].split(";");
  				for (String sig : arr)
  					list.add(sig);
  				nd.setFieldValue(pif, list);
  				break;
  			default:
  			}
          }
      }
  	
  	private static String origStr(String clean){
          try {
              return URLDecoder.decode(clean, "UTF-8");
          } catch (UnsupportedEncodingException e) {
              Log.e(TAG, "UTF-8 unsupported when decoding: "+clean);
          } 
          return URLDecoder.decode(clean);
      }
  	
  	private static String cleanStr(String dirty){
  		if (dirty == null)
  			return null;
  		/*
  		 * At least the Norwegian translation of Samsung's Story Album has a newline in the app name.
  		 */
  		String d = dirty.replace('\n', ' ').replace(';', ',');
  	    try {
              return URLEncoder.encode(d, "UTF-8");
          } catch (UnsupportedEncodingException e) {
              Log.e(TAG, "UTF-8 unsupported when encoding: "+d);
          }
  	    return URLEncoder.encode(d).replace('\n', ' ').replace(';', ',');
  	}
  }
  