Package org.tmatesoft.hg.internal

Source Code of org.tmatesoft.hg.internal.Metadata$Record

/*
* Copyright (c) 2013 TMate Software Ltd
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* For information on how to redistribute this software under
* the terms of a license other than GNU General Public License
* contact TMate Software at support@hg4j.com
*/
package org.tmatesoft.hg.internal;

import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION;
import static org.tmatesoft.hg.util.LogFacility.Severity.Error;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;

import org.tmatesoft.hg.core.SessionContext;
import org.tmatesoft.hg.repo.HgInvalidControlFileException;
import org.tmatesoft.hg.repo.HgInvalidStateException;
import org.tmatesoft.hg.util.LogFacility;

/**
* Container for metadata recorded as part of file revisions
*
* @author Artem Tikhomirov
* @author TMate Software Ltd.
*/
public final class Metadata {
  private static class Record {
    public final int offset;
    public final MetadataEntry[] entries;
   
    public Record(int off, MetadataEntry[] entr) {
      offset = off;
      entries = entr;
    }
  }
  // XXX sparse array needed
  private final IntMap<Metadata.Record> entries = new IntMap<Metadata.Record>(5);
 
  private final Metadata.Record NONE = new Record(-1, null); // don't want statics

  private final LogFacility log;
 
  private int lastRevRead = NO_REVISION;

  public Metadata(SessionContext.Source sessionCtx) {
    log = sessionCtx.getSessionContext().getLog();
  }
 
  /**
   * @return <code>true</code> when there's metadata for given revision
   */
  public boolean known(int revision) {
    Metadata.Record i = entries.get(revision);
    return i != null && NONE != i;
  }

  /**
   * @return <code>true</code> when revision has been checked for metadata presence.
   */
  public boolean checked(int revision) {
    return entries.containsKey(revision);
  }

  // true when revision has been checked and found not having any metadata
  public boolean none(int revision) {
    Metadata.Record i = entries.get(revision);
    return i == NONE;
  }
 
  /**
   * Get the greatest revision index visited so far.
   * Note, doesn't imply all revisions up to this has been visited.
   */
  public int lastRevisionRead() {
    return lastRevRead;
  }

  // mark revision as having no metadata.
  void recordNone(int revision) {
    Metadata.Record i = entries.get(revision);
    if (i == NONE) {
      return; // already there
    }
    if (i != null) {
      throw new HgInvalidStateException(String.format("Trying to override Metadata state for revision %d (known offset: %d)", revision, i));
    }
    entries.put(revision, NONE);
  }

  // since this is internal class, callers are supposed to ensure arg correctness (i.e. ask known() before)
  public int dataOffset(int revision) {
    return entries.get(revision).offset;
  }
  void add(int revision, int dataOffset, Collection<MetadataEntry> e) {
    assert !entries.containsKey(revision);
    entries.put(revision, new Record(dataOffset, e.toArray(new MetadataEntry[e.size()])));
  }
 
  /**
   * @return <code>true</code> if metadata has been found
   */
  public boolean tryRead(int revisionNumber, DataAccess data) throws IOException, HgInvalidControlFileException {
    final int daLength = data.length();
    if (lastRevRead == NO_REVISION || revisionNumber > lastRevRead) {
      lastRevRead = revisionNumber;
    }
    if (daLength < 4 || data.readByte() != 1 || data.readByte() != 10) {
      recordNone(revisionNumber);
      return false;
    } else {
      ArrayList<MetadataEntry> _metadata = new ArrayList<MetadataEntry>();
      int offset = parseMetadata(data, daLength, _metadata);
      add(revisionNumber, offset, _metadata);
      return true;
    }
  }

  public String find(int revision, String key) {
    for (MetadataEntry me : entries.get(revision).entries) {
      if (me.matchKey(key)) {
        return me.value();
      }
    }
    return null;
  }

  private int parseMetadata(DataAccess data, final int daLength, ArrayList<MetadataEntry> _metadata) throws IOException, HgInvalidControlFileException {
    int lastEntryStart = 2;
    int lastColon = -1;
    // XXX in fact, need smth like ByteArrayBuilder, similar to StringBuilder,
    // which can't be used here because we can't convert bytes to chars as we read them
    // (there might be multi-byte encoding), and we need to collect all bytes before converting to string
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    String key = null, value = null;
    boolean byteOne = false;
    boolean metadataIsComplete = false;
    for (int i = 2; i < daLength; i++) {
      byte b = data.readByte();
      if (b == '\n') {
        if (byteOne) { // i.e. \n follows 1
          lastEntryStart = i+1;
          metadataIsComplete = true;
          // XXX is it possible to have here incomplete key/value (i.e. if last pair didn't end with \n)
          // if yes, need to set metadataIsComplete to true in that case as well
          break;
        }
        if (key == null || lastColon == -1 || i <= lastColon) {
          log.dump(getClass(), Error, "Missing key in file revision metadata at index %d", i);
        }
        value = new String(bos.toByteArray()).trim();
        bos.reset();
        _metadata.add(new MetadataEntry(key, value));
        key = value = null;
        lastColon = -1;
        lastEntryStart = i+1;
        continue;
      }
      // byteOne has to be consumed up to this line, if not yet, consume it
      if (byteOne) {
        // insert 1 we've read on previous step into the byte builder
        bos.write(1);
        byteOne = false;
        // fall-through to consume current byte
      }
      if (b == (int) ':') {
        assert value == null;
        key = new String(bos.toByteArray());
        bos.reset();
        lastColon = i;
      } else if (b == 1) {
        byteOne = true;
      } else {
        bos.write(b);
      }
    }
    // data.isEmpty is not reliable, renamed files of size==0 keep only metadata
    if (!metadataIsComplete) {
      // XXX perhaps, worth a testcase (empty file, renamed, read or ask ifCopy
      throw new HgInvalidControlFileException("Metadata is not closed properly", null, null);
    }
    return lastEntryStart;
  }

  /**
   * There may be several entries of metadata per single revision, this class captures single entry
   */
  private static class MetadataEntry {
    private final String entry;
    private final int valueStart;

    // key may be null
    /* package-local */MetadataEntry(String key, String value) {
      if (key == null) {
        entry = value;
        valueStart = -1; // not 0 to tell between key == null and key == ""
      } else {
        entry = key + value;
        valueStart = key.length();
      }
    }

    /* package-local */boolean matchKey(String key) {
      return key == null ? valueStart == -1 : key.length() == valueStart && entry.startsWith(key);
    }

//      uncomment once/if needed
//      public String key() {
//        return entry.substring(0, valueStart);
//      }

    public String value() {
      return valueStart == -1 ? entry : entry.substring(valueStart);
    }
  }
}
TOP

Related Classes of org.tmatesoft.hg.internal.Metadata$Record

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.