Package com.google.api.ads.adwords.awreporting.model.csv

Source Code of com.google.api.ads.adwords.awreporting.model.csv.AnnotationBasedMappingStrategy

// Copyright 2013 Google Inc. All Rights Reserved.
//
// 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.google.api.ads.adwords.awreporting.model.csv;

import com.google.api.ads.adwords.awreporting.model.csv.annotation.CsvField;
import com.google.api.ads.adwords.awreporting.model.csv.annotation.MoneyField;
import com.google.api.ads.adwords.awreporting.model.entities.Report;

import au.com.bytecode.opencsv.CSVReader;
import au.com.bytecode.opencsv.bean.CsvToBean;
import au.com.bytecode.opencsv.bean.MappingStrategy;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

/**
* This class describes the mapping strategy to convert CSV files into Java beans using annotations.
*
* {@code AnnotationBasedMappingStrategy} is an implementation of {@link MappingStrategy}. In order
* to use this class, you need to instantiate a {@code AnnotationBasedMappingStrategy} with the
* proper {@link CSVReader}, and use both classes to construct a {@link CsvToBean}.
*
* The {@code AnnotationBasedMappingStrategy} is expecting to map only Java beans that extend from
* {@link Report}. A code example on how to use this mapping strategy:
*
* <pre>    {@code
* CSVReader csvReader = new AwReportCsvReader(new InputStreamReader(
*    new FileInputStream("file-name"), "UTF-8"), ',', '\"', 1);
*
* AnnotationBasedMappingStrategy<ReportAd> mappingStrategy =
*    new AnnotationBasedMappingStrategy<ReportAd>(ReportAd.class);
*
* CsvToBean<ReportAd> csvToBean = new CsvToBean<ReportAd>();
* List<ReportAd> parsedBeans = csvToBean.parse(mappingStrategy, csvReader);
*}</pre>
*
* The example uses a {@link AwReportCsvReader} in order to parse the CSV files from the AdWords
* report API.
*
* @author gustavomoreira@google.com (Gustavo Moreira)
* @author juliantoledo@google.com (Julian Toledo)
*
* @param <T> type of sub Report.
*/
public class AnnotationBasedMappingStrategy<T extends Report> implements MappingStrategy<T>, Serializable {

  private static final long serialVersionUID = 1L;

  private Class<T> reportEntityClass;

  private final Map<Integer, String> csvIndexToReportNames = new HashMap<Integer, String>();
 
  private final Map<String, Boolean> fieldsWithMoneyValues = new HashMap<String, Boolean>();

  /**
   * C'tor
   *
   * @param reportEntityClass the {@code class} of the report entity POJO. This parameter is
   *        obligatory.
   */
  public AnnotationBasedMappingStrategy(Class<T> reportEntityClass) {

    if (reportEntityClass == null) {
      throw new NullPointerException("The report entity class must be especified.");
    }
    this.reportEntityClass = reportEntityClass;
  }

  /**
   * Captures the header of the CSV file.
   *
   *  This method scans the class of the bean for the proper annotations, and associate the correct
   * column index to the correct field.
   *
   * @param csvReader the {@code CSVReader}
   */
  @Override
  public void captureHeader(CSVReader csvReader) throws IOException {

    String[] header = csvReader.readNext();

    Map<String, String> nameMapping = this.createNameMapping();

    for (int i = 0; i < header.length; i++) {
      this.csvIndexToReportNames.put(i, nameMapping.get(header[i]));
    }
  }

  /**
   * Creates the {@code Map} where the key is the name of the property in the CSV file, and the
   * value is the name of the field in the Java bean.
   *
   * @return the {@code Map} of the properties for the bean class.
   */
  private Map<String, String> createNameMapping() {

    Map<String, String> nameMapping = new HashMap<String, String>();

    Class<?> currentClass = this.reportEntityClass;
    while (currentClass != Object.class) {

      this.addNameMappingForDeclaredFields(nameMapping, currentClass);
      currentClass = currentClass.getSuperclass();
    }
    return nameMapping;
  }

  /**
   * Scans the current class' fields for the mapped properties, and associate then to the CSV file
   * columns.
   *
   * @param nameMapping the map to be filled.
   * @param currentClass the class to be scanned.
   */
  private void addNameMappingForDeclaredFields(
      Map<String, String> nameMapping, Class<?> currentClass) {
    Field[] declaredFields = currentClass.getDeclaredFields();

    if (declaredFields.length > 0) {
      for (Field field : declaredFields) {
        this.addNameMappingIfAnnotationPresent(nameMapping, field);
        this.addMoneyMappingIfAnnotationPresent(field);
      }
    }
  }

  /**
   * Checks for the annotation, and if the annotation is present, creates the association between
   * the CSV property and the field.
   *
   * @param nameMapping the map that is being filled.
   * @param field the current field.
   */
  private void addNameMappingIfAnnotationPresent(Map<String, String> nameMapping, Field field) {

    if (field.isAnnotationPresent(CsvField.class)) {

      CsvField reportFieldAnnotation = field.getAnnotation(CsvField.class);
      String csvFieldName = reportFieldAnnotation.value();
      nameMapping.put(csvFieldName, field.getName());
    }
  }

  /**
   * Checks for the @MoneyField annotation, and if the annotation is present, puts a true value at
   * fieldsWithMoneyValues, this value will be used when parsing CSV to use BigDecimal and divide by 1M.
   *
   * @param field the current field.
   */
  private void addMoneyMappingIfAnnotationPresent(Field field) {
    if (field.isAnnotationPresent(MoneyField.class)) {
      fieldsWithMoneyValues.put(field.getName(), true);
    }
  }

  /**
   * Creates a new instance of the Java bean.
   *
   *  The Java bean must have a public constructor that receives no arguments in order to be
   * instantiated.
   *
   */
  @Override
  public T createBean() throws InstantiationException, IllegalAccessException {

    try {
      return this.reportEntityClass.getConstructor().newInstance();

    } catch (NoSuchMethodException e) {
      throw new InstantiationException("Could not instantiate "
          + this.reportEntityClass.getCanonicalName() + ". "
          + "The class must have a public constructor with no arguments.");
    } catch (SecurityException e) {
      throw new IllegalAccessException("Could not instantiate "
          + this.reportEntityClass.getCanonicalName() + ". "
          + "The class must have a public constructor with no arguments.");
    } catch (IllegalArgumentException e) {
      throw new InstantiationException("Could not instantiate "
          + this.reportEntityClass.getCanonicalName() + ". "
          + "The class must have a public constructor with no arguments.");
    } catch (InvocationTargetException e) {
      throw new InstantiationException("Could not instantiate "
          + this.reportEntityClass.getCanonicalName() + ". "
          + "The class must have a public constructor with no arguments.");
    }
  }

  /**
   * Find the property descriptor that is referenced by the given column index.
   *
   * The mapping between the indexes and the field were created when the CSV header was captured.
   */
  @Override
  public PropertyDescriptor findDescriptor(int columnNumber) throws IntrospectionException {

    String propertyName = this.csvIndexToReportNames.get(columnNumber);
    if (propertyName != null) {
      return new PropertyDescriptor(propertyName, this.reportEntityClass);
    }
    return null;
  }

  /**
   * Checks if a field on the class has the annotations @MoneyField,
   * this value will be used when parsing CSV to use BigDecimal and divide by 1M.
   */
  public boolean isMoneyField(String field) {
    if (fieldsWithMoneyValues.containsKey(field)) {
      return fieldsWithMoneyValues.get(field);
    } else {
      return false;
    }
  }
}
TOP

Related Classes of com.google.api.ads.adwords.awreporting.model.csv.AnnotationBasedMappingStrategy

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.