tikhomirov@384: /* tikhomirov@618: * Copyright (c) 2012-2013 TMate Software Ltd tikhomirov@384: * tikhomirov@384: * This program is free software; you can redistribute it and/or modify tikhomirov@384: * it under the terms of the GNU General Public License as published by tikhomirov@384: * the Free Software Foundation; version 2 of the License. tikhomirov@384: * tikhomirov@384: * This program is distributed in the hope that it will be useful, tikhomirov@384: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@384: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@384: * GNU General Public License for more details. tikhomirov@384: * tikhomirov@384: * For information on how to redistribute this software under tikhomirov@384: * the terms of a license other than GNU General Public License tikhomirov@384: * contact TMate Software at support@hg4j.com tikhomirov@384: */ tikhomirov@538: package org.tmatesoft.hg.internal; tikhomirov@384: tikhomirov@618: import java.io.ByteArrayOutputStream; tikhomirov@384: import java.util.ArrayList; tikhomirov@538: import java.util.Collection; tikhomirov@384: import java.util.Collections; tikhomirov@384: import java.util.Iterator; tikhomirov@384: import java.util.LinkedHashMap; tikhomirov@384: import java.util.List; tikhomirov@384: import java.util.Map; tikhomirov@384: import java.util.Map.Entry; tikhomirov@618: import java.util.TimeZone; tikhomirov@384: tikhomirov@618: import org.tmatesoft.hg.core.HgIOException; tikhomirov@384: import org.tmatesoft.hg.core.Nodeid; tikhomirov@618: import org.tmatesoft.hg.internal.DataSerializer.DataSource; tikhomirov@538: import org.tmatesoft.hg.util.Path; tikhomirov@384: tikhomirov@384: /** tikhomirov@618: * Builds changelog entry tikhomirov@384: * @author Artem Tikhomirov tikhomirov@384: * @author TMate Software Ltd. tikhomirov@384: */ tikhomirov@618: public class ChangelogEntryBuilder implements DataSource { tikhomirov@384: tikhomirov@384: private String user; tikhomirov@538: private List modifiedFiles; tikhomirov@384: private final Map extrasMap = new LinkedHashMap(); tikhomirov@384: private Integer tzOffset; tikhomirov@384: private Long csetTime; tikhomirov@618: private Nodeid manifestRev; tikhomirov@618: private CharSequence comment; tikhomirov@384: tikhomirov@384: public ChangelogEntryBuilder user(String username) { tikhomirov@384: user = username; tikhomirov@384: return this; tikhomirov@384: } tikhomirov@384: tikhomirov@384: public String user() { tikhomirov@384: if (user == null) { tikhomirov@384: // for our testing purposes anything but null is ok. no reason to follow Hg username lookup conventions tikhomirov@384: user = System.getProperty("user.name"); tikhomirov@384: } tikhomirov@384: return user; tikhomirov@384: } tikhomirov@384: tikhomirov@538: public ChangelogEntryBuilder setModified(Collection files) { tikhomirov@538: modifiedFiles = new ArrayList(files == null ? Collections.emptyList() : files); tikhomirov@384: return this; tikhomirov@384: } tikhomirov@384: tikhomirov@538: public ChangelogEntryBuilder addModified(Collection files) { tikhomirov@384: if (modifiedFiles == null) { tikhomirov@384: return setModified(files); tikhomirov@384: } tikhomirov@384: modifiedFiles.addAll(files); tikhomirov@384: return this; tikhomirov@384: } tikhomirov@384: tikhomirov@384: public ChangelogEntryBuilder branch(String branchName) { tikhomirov@386: if (branchName == null || "default".equals(branchName)) { tikhomirov@384: extrasMap.remove("branch"); tikhomirov@384: } else { tikhomirov@384: extrasMap.put("branch", branchName); tikhomirov@384: } tikhomirov@384: return this; tikhomirov@384: } tikhomirov@384: tikhomirov@384: public ChangelogEntryBuilder extras(Map extras) { tikhomirov@384: extrasMap.clear(); tikhomirov@384: extrasMap.putAll(extras); tikhomirov@384: return this; tikhomirov@384: } tikhomirov@384: tikhomirov@384: public ChangelogEntryBuilder date(long seconds, int timezoneOffset) { tikhomirov@384: csetTime = seconds; tikhomirov@384: tzOffset = timezoneOffset; tikhomirov@384: return this; tikhomirov@384: } tikhomirov@384: tikhomirov@618: public ChangelogEntryBuilder manifest(Nodeid manifestRevision) { tikhomirov@618: manifestRev = manifestRevision; tikhomirov@618: return this; tikhomirov@618: } tikhomirov@618: tikhomirov@618: public ChangelogEntryBuilder comment(CharSequence commentString) { tikhomirov@618: comment = commentString; tikhomirov@618: return this; tikhomirov@618: } tikhomirov@618: tikhomirov@618: public void serialize(DataSerializer out) throws HgIOException { tikhomirov@618: byte[] b = build(); tikhomirov@618: out.write(b, 0, b.length); tikhomirov@618: } tikhomirov@618: tikhomirov@618: public int serializeLength() { tikhomirov@618: return -1; tikhomirov@618: } tikhomirov@618: tikhomirov@618: public byte[] build() { tikhomirov@618: ByteArrayOutputStream out = new ByteArrayOutputStream(); tikhomirov@618: final int LF = '\n'; tikhomirov@618: CharSequence extras = buildExtras(); tikhomirov@618: CharSequence files = buildFiles(); tikhomirov@618: byte[] manifestRevision = manifestRev.toString().getBytes(); tikhomirov@618: byte[] username = user().getBytes(EncodingHelper.getUTF8()); tikhomirov@618: out.write(manifestRevision, 0, manifestRevision.length); tikhomirov@618: out.write(LF); tikhomirov@618: out.write(username, 0, username.length); tikhomirov@618: out.write(LF); tikhomirov@618: final long csetDate = csetTime(); tikhomirov@618: byte[] date = String.format("%d %d", csetDate, csetTimezone(csetDate)).getBytes(); tikhomirov@618: out.write(date, 0, date.length); tikhomirov@618: if (extras.length() > 0) { tikhomirov@618: out.write(' '); tikhomirov@618: byte[] b = extras.toString().getBytes(); tikhomirov@618: out.write(b, 0, b.length); tikhomirov@618: } tikhomirov@618: out.write(LF); tikhomirov@618: byte[] b = files.toString().getBytes(); tikhomirov@618: out.write(b, 0, b.length); tikhomirov@618: out.write(LF); tikhomirov@618: out.write(LF); tikhomirov@618: byte[] cmt = comment.toString().getBytes(EncodingHelper.getUTF8()); tikhomirov@618: out.write(cmt, 0, cmt.length); tikhomirov@618: return out.toByteArray(); tikhomirov@618: } tikhomirov@618: tikhomirov@618: private CharSequence buildExtras() { tikhomirov@618: StringBuilder extras = new StringBuilder(); tikhomirov@618: for (Iterator> it = extrasMap.entrySet().iterator(); it.hasNext();) { tikhomirov@618: final Entry next = it.next(); tikhomirov@618: extras.append(encodeExtrasPair(next.getKey())); tikhomirov@618: extras.append(':'); tikhomirov@618: extras.append(encodeExtrasPair(next.getValue())); tikhomirov@618: if (it.hasNext()) { tikhomirov@618: extras.append('\00'); tikhomirov@618: } tikhomirov@618: } tikhomirov@618: return extras; tikhomirov@618: } tikhomirov@618: tikhomirov@618: private CharSequence buildFiles() { tikhomirov@618: StringBuilder files = new StringBuilder(); tikhomirov@618: if (modifiedFiles != null) { tikhomirov@618: Collections.sort(modifiedFiles); tikhomirov@618: for (Iterator it = modifiedFiles.iterator(); it.hasNext(); ) { tikhomirov@618: files.append(it.next()); tikhomirov@618: if (it.hasNext()) { tikhomirov@618: files.append('\n'); tikhomirov@618: } tikhomirov@618: } tikhomirov@618: } tikhomirov@618: return files; tikhomirov@618: } tikhomirov@618: tikhomirov@618: private final static CharSequence encodeExtrasPair(String s) { tikhomirov@618: if (s != null) { tikhomirov@618: return s.replace("\\", "\\\\").replace("\n", "\\n").replace("\r", "\\r").replace("\00", "\\0"); tikhomirov@618: } tikhomirov@618: return s; tikhomirov@618: } tikhomirov@618: tikhomirov@384: private long csetTime() { tikhomirov@384: if (csetTime != null) { tikhomirov@384: return csetTime; tikhomirov@384: } tikhomirov@384: return System.currentTimeMillis() / 1000; tikhomirov@384: } tikhomirov@384: tikhomirov@384: private int csetTimezone(long time) { tikhomirov@384: if (tzOffset != null) { tikhomirov@384: return tzOffset; tikhomirov@384: } tikhomirov@384: return -(TimeZone.getDefault().getOffset(time) / 1000); tikhomirov@384: } tikhomirov@384: }