view mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommitsAndMountPointsParser.java @ 919:16ad8d51b001

fix non-ascii characters support in commandline arguments
author eugene.petrenko@jetbrains.com
date Mon, 19 Jan 2015 18:30:08 +0100
parents fba173530aa6
children 7bf4d943d5bb
line wrap: on
line source
/*
 * Copyright 2000-2014 JetBrains s.r.o.
 *
 * 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 jetbrains.buildServer.buildTriggers.vcs.mercurial.command;

import jetbrains.buildServer.util.FileUtil;
import org.apache.commons.codec.binary.Base64;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Created 09.01.14 17:10
 *
 * @author Eugene Petrenko (eugene.petrenko@jetbrains.com)
 */
public class CommitsAndMountPointsParser {

  public static void parseFileLog(@NotNull final File dump,
                                  @NotNull final ContentsConsumer consumer) throws IOException {
    final BufferedReader st = new BufferedReader(new InputStreamReader(new BufferedInputStream(new FileInputStream(dump)), "utf-8"));
    try {
      final Decoder fileDecoder = new Decoder(5);

      String line;
      while((line = st.readLine()) != null) {
        if (!line.startsWith("$$@@@@ ")) continue;
        final String[] items = line.split(" ");
        if (items.length != 1 + 2) continue;

        final String commitId = items[1];
        final String content = fileDecoder.decode(items[2]);

        consumer.onCommit(commitId, content == null ? "" : content);
      }
    } finally {
      FileUtil.close(st);
    }
  }

  public interface ContentsConsumer {
    void onCommit(
            @NotNull String fileNodeId,
            @NotNull String content);
  }

  public interface CommitsConsumer {
    void onCommit(
            @NotNull String commitNum,
            @NotNull String commitId,
            @NotNull String[] parents,
            @NotNull String branch,
            @NotNull String[] tags,
            @NotNull String author,
            @NotNull String message,
            @NotNull Date timestamp,
            @Nullable String hgsubNodeId,
            @Nullable String hgsubstateNodeId);
  }


  public static void parseCommits(@NotNull final File dump, @NotNull final CommitsConsumer consumer) throws IOException {
    final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'Z'HH:mm:ss'T'Z", Locale.ENGLISH);

    final Decoder branchDecoder = new Decoder(250);
    final Decoder tagsDecoder = new Decoder(250);
    final Decoder authorDecoder = new Decoder(200);
    final Decoder messageDecoder = new Decoder(210);

    final BufferedReader st = new BufferedReader(new InputStreamReader(new BufferedInputStream(new FileInputStream(dump)), "utf-8"));
    try {
      String line;
      while((line = st.readLine()) != null) {
        if (!line.startsWith("$$@@@@ ")) continue;
        final Iterator<String> items = Arrays.asList(line.split(" ")).iterator();
        items.next(); //$$@@@@

        try {
        final String commitNum = items.next();
        final String commitId = items.next();
        final String[] parents = new String[Integer.parseInt(items.next())];
        for (int i = 0; i < parents.length; i++) {
          parents[i] = items.next();
        }
        final String branch = branchDecoder.decode(items.next());
        final String[] tags = new String[Integer.parseInt(items.next())];
        for (int i = 0; i < tags.length; i++) {
          tags[i] = tagsDecoder.decode(items.next());
        }

        final String author = authorDecoder.decode(items.next());
        final String message = messageDecoder.decode(items.next());
        final Date time = parseTime(dateFormat, items.next());
        final String hgsub = textOrNull(items.next());
        final String hgsubstate = textOrNull(items.next());

        consumer.onCommit(
                commitNum,
                commitId,
                parents,
                branch,
                tags,
                author == null ? "" : author,
                message == null ? "" : message,
                time,
                hgsub,
                hgsubstate);
        } catch (NoSuchElementException e) {
          //NOP
        }
      }
    } finally{
      FileUtil.close(st);
    }
  }

  @NotNull
  private static Date parseTime(@NotNull final SimpleDateFormat dateFormat,
                                @NotNull final String time) {
    try {
      return dateFormat.parse(time);
    } catch (ParseException e) {
      throw new RuntimeException("Failed to parse datetime: " + time + ". " + e, e);
    }
  }

  @Nullable
  private static String textOrNull(@NotNull final String text) {
    if (text.equals("=====")) return null;
    return text;
  }

  private static class Decoder {
    private final Map<String, String> myCache;

    public Decoder(final int SZ) {
      myCache = new LinkedHashMap<String, String>(SZ, 0.9f) {
        @Override
        protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
          return size() > SZ;
        }
      };
    }

    public String decode(@NotNull final String base64) throws UnsupportedEncodingException {
      if (textOrNull(base64) == null) return null;

      final String result = myCache.get(base64);
      if (result != null) return result;

      final String value = new String(Base64.decodeBase64(base64), "utf-8");
      //noinspection RedundantStringConstructorCall
      myCache.put(new String(base64), value);
      return value;
    }
  }
}