content: package com.taobao.loganalyzer.input.tanxpv.parser;
  
  import com.taobao.loganalyzer.input.tanxpv.common.LogField;
  import com.taobao.loganalyzer.input.tanxpv.common.LogParser;
  import com.taobao.loganalyzer.input.tanxpv.common.LogRecord;
  import com.taobao.loganalyzer.input.tanxpv.common.SectionParser;
  
  /**
   * Tanx PV日志的解析
   * @version 1.0
   * @see <a href="http://sps.corp.alimama.com/ad/ADExchange/DocLib/Tan(X)%E7%B3%BB%E7%BB%9F%E6%97%A5%E5%BF%97%E8%AE%BE%E8%AE%A1.pdf">点击日志格式</a>
   * @author kangtian
   *
   */
  public class TanxPVLogParser {
      private static SectionParser[] sp = {
              new VersionSectionParser(),
              new CommonSectionParser(),
              new SiteSectionParser(),
              new AdzoneSectionParser(),
              new PageSectionParser(),
              new AdSectionParser(),
              new AntiFraudSectionParser(),
              new UserSectionParser(),
              new CPASectionParser(),
              new DefaultADSectionParser(),
              new VerifySectionParser(),
              new WirlessSectionParser()
      };
      private static final int PV_SECTIONS = sp.length;
  
      /**
       * 解析Tanx PV日志中指定的section，当前版本的section个数为11
       * @param line 需要解析的pv日志
       * @param flags 需要解析的pv日志的section的列表，true为需要解析，false为不需要解析；建议熟悉底层解析逻辑者使用。
       * @return 解析的结果，存储在hashtable中，null表示格式不符合
       */
      public static LogRecord parseTanxPV(String line, boolean[] flags) {
          if (flags.length != PV_SECTIONS) {
              throw new RuntimeException("Expected " + flags.length + " sections, but only " + PV_SECTIONS + " section exist.");
          }
  
          // important:版本1.0不记录如下字段，因此，我没改相应的sectionoParpser的东西，如果以后需要记录，则要注意修改相应的sectionParser的所有内容。
          //skip section 5 : PageSection ;
          //skip section 8 : UserSection ---> don't skip @20110509;
          //skip section 9 : CPASection ;
          //skip section 10: defaultADsection ;
  
          //flags[4] = false ;don't skip any more
          //flags[7] = false ; don't skip any more
          flags[8] = false ;
          flags[9] = false ;
  
          LogRecord lr = new LogRecord();
          LogParser lp = new LogParser(line);
  
          for (int i = 0; i < PV_SECTIONS; i++) {
              if (flags[i] == true) {
                  boolean ret = sp[i].parse(lp, lr);
                  if (ret == false) {
                      System.err.println(sp[i].getClass() + " run error");
                      return null;
                  }
              } else {
                  if (lp.skipNextFieldCA() == false && i != sp.length-1) {
                      return null;
                  }
              }
          }
  
          if (!check(lr)) return null;
  
          return lr;
      }
  
      /**
       * 解析Tanx PV日志中指定的section，当前版本的section个数为12
       * @param line 需要解析的pv日志
       * @param flags 需要解析的pv日志的section的列表，true为需要解析，false为不需要解析；建议熟悉底层解析逻辑者使用。
       * @return 解析的结果，存储在TanxPVLog中，null表示格式不符合
       */
      public static TanxPVLog parse(String line, boolean[] flags) {
          LogRecord lr = parseTanxPV(line, flags);
          if (lr == null) return null;
          return new TanxPVLog(lr);
      }
  
      /**
       * 解析该Tanx PV日志的所有section
       * <br>日志版本号字段不能为空,若为空，解析失败。
       * <br>parser会对日志进行验证，取校验Section和共用section 的Session ID的前4个bytes进行校验，若两者不相等，会解析失败。
       * @param line 需要解析的PV日志
       * @return 解析的结果，存储在TanxPVLog中，null表示格式不符合
       */
      public static TanxPVLog parse(String line) {
          boolean[] flags = new boolean[]{true, true, true, true, true, true, true, true, true, true, true, true};
          return parse(line, flags);
      }
  
      private static boolean check(LogRecord lr) {
          // 校验Section：1.2取Session ID的前4个bytes
          String verify = getFieldContent(lr, "Verify");
  
          String sessionID = getFieldContent(lr, "SessionID");
          if (verify == null || verify.length() != 4 || sessionID == null || !sessionID.startsWith(verify))
              return false;
  
          return true;
      }
  
      private static String getFieldContent(LogRecord lr, String name) {
          if (lr != null) {
              LogField lf = lr.getField(name);
              return lf==null?null:(String)lf.getContent();
          }
          return null;
      };
  
  }
  
  