All Downloads are FREE. Search and download functionalities are using the official Maven repository.

br.com.objectos.git.ResolveRef Maven / Gradle / Ivy

/*
 * Copyright (C) 2020-2022 Objectos Software LTDA.
 *
 * 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 br.com.objectos.git;

import br.com.objectos.fs.PathName;
import br.com.objectos.fs.RegularFile;
import br.com.objectos.fs.ResolvedPath;
import br.com.objectos.fs.SimplePathNameVisitor;
import br.com.objectos.logging.Event1;
import br.com.objectos.logging.Event2;
import br.com.objectos.logging.Events;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;

/**
 * Resolves a Git reference.
 *
 * @since 1
 */
final class ResolveRef extends AbstractGitEngineTask {

  private static final byte _CLOSE = 1;

  private static final byte _DECODE = 2;

  private static final byte _LOOSE = 3;

  private static final byte _MAYBE_PACKED = 4;

  private static final byte _NULL_OBJECT = 5;

  private static final byte _PACKED = 6;

  private static final byte _PARSE_PACKED = 7;

  private static final Event2 ESTART;

  private static final Event1 ESUCCESS;

  private static final byte IO_CLOSE = 1;

  private static final byte IO_READ = 2;

  private static final byte IO_RESOLVE_LOOSE = 3;

  private static final byte IO_RESOLVE_PACKED = 4;

  static {
    Class s;
    s = ResolveRef.class;

    ESTART = Events.debug(s, "START", Repository.class, RefName.class);

    ESUCCESS = Events.debug(s, "SUCCESS", MaybeObjectId.class);
  }

  /*
  
  @startuml
  
  hide empty description
  skinparam shadowing false
  
  [*] --> IO : ioAction=resolveLoose\lioReady=MAYBE_LOOSE
  
  FINALLY --> [*]
  
  IO --> IO_WAIT
  
  IO_WAIT --> MAYBE_LOOSE
  IO_WAIT --> MAYBE_PACKED
  
  MAYBE_LOOSE --> OBJECT_ID
  MAYBE_LOOSE --> IO : ioAction=resolvePacked\lioReady=MAYBE_PACKED
  
  MAYBE_PACKED --> NULL
  MAYBE_PACKED --> PARSE
  
  OBJECT_ID --> FINALLY
  
  PARSE --> OBJECT_ID
  PARSE --> IO : ioAction=readPacked\lioReady=PARSE
  PARSE --> NULL
  
  NULL --> FINALLY
  
  @enduml
  
  */

  private FileChannel channel;

  private ByteBuffer channelBuffer;

  private long channelReadCount;

  private long channelReadLimit;

  private CharsetDecoder decoder;

  private CharBuffer decoderBuffer;

  private byte decodeReady;

  private PackedParser parser;

  private RefName ref;

  private Repository repository;

  private StringBuilder stringBuilder;

  ResolveRef(GitInjector injector) {
    super(injector);

  }

  public final void setInput(Repository repository, RefName ref) {
    checkSetInput();

    this.repository = repository;

    this.ref = ref;
  }

  @Override
  final byte errorState() {
    return _CLOSE;
  }

  @Override
  final byte execute(byte state) {
    switch (state) {
      case _CLOSE:
        return executeClose();
      case _DECODE:
        return executeDecode();
      case _LOOSE:
        return executeLoose();
      case _MAYBE_PACKED:
        return executeMaybePacked();
      case _NULL_OBJECT:
        return executeNullObject();
      case _PACKED:
        return executePacked();
      case _PARSE_PACKED:
        return executeParsePacked();
      default:
        return super.execute(state);
    }
  }

  @Override
  final byte executeFinally() {
    channel = null;

    channelBuffer = injector.putByteBuffer(channelBuffer);

    channelReadCount = 0;

    channelReadLimit = 0;

    decoder = injector.putDecoder(decoder);

    decoderBuffer = injector.putCharBuffer(decoderBuffer);

    decodeReady = 0;

    parser = null;

    ref = null;

    repository = null;

    stringBuilder = injector.putStringBuilder(stringBuilder);

    return super.executeFinally();
  }

  @Override
  final void executeIo(byte ioTask) throws GitStubException, IOException {
    switch (ioTask) {
      case IO_CLOSE:
        ioClose();
        break;
      case IO_READ:
        ioRead();
        break;
      case IO_RESOLVE_LOOSE:
        ioResolveLoose();
        break;
      case IO_RESOLVE_PACKED:
        ioResolvePacked();
        break;
      default:
        super.executeIo(ioTask);
        break;
    }
  }

  @Override
  final byte executeStart() {
    super.executeStart();

    channelBuffer = injector.getByteBuffer();

    Charset charset;
    charset = repository.getCharset();

    decoder = injector.getDecoder(charset);

    decoderBuffer = injector.getCharBuffer();

    stringBuilder = injector.getStringBuilder();

    log(ESTART, repository, ref);

    return toIo(
        IO_RESOLVE_LOOSE, toDecode(_LOOSE), _CLOSE
    );
  }

  final void openFile(RegularFile file) throws IOException {
    channel = file.openReadChannel();

    channel.position(0);

    channelBuffer.clear();

    channelReadCount = 0;

    channelReadLimit = channel.size();

    ioRead();
  }

  private byte executeClose() {
    if (channel != null) {
      return toIo(IO_CLOSE, _FINALLY, _FINALLY);
    } else {
      return _FINALLY;
    }
  }

  private byte executeDecode() {
    if (decoderBuffer.position() > 0) {
      decoderBuffer.compact();
    }

    int limit;
    limit = channelBuffer.limit();

    int position;
    position = channelBuffer.position();

    int preventOverflowLimit;
    preventOverflowLimit = position + decoderBuffer.remaining();

    boolean endOfInput;
    endOfInput = isChannelEof();

    if (preventOverflowLimit < limit) {
      channelBuffer.limit(preventOverflowLimit);

      endOfInput = false;
    }

    CoderResult coderResult;
    coderResult = decoder.decode(channelBuffer, decoderBuffer, endOfInput);

    channelBuffer.limit(limit);

    if (!coderResult.isUnderflow()) {
      return toStubException("codeResult != underflow");
    }

    decoderBuffer.flip();

    return decodeReady;
  }

  private byte executeLoose() {
    stringBuilder.append(decoderBuffer);

    if (!isChannelEof()) {
      return toIo(
          IO_READ, toDecode(_LOOSE), _CLOSE
      );
    }

    try {
      ObjectId objectId;
      objectId = ObjectId.parse(stringBuilder, 0);

      setResult(objectId);

      return _CLOSE;
    } catch (InvalidObjectIdFormatException e) {
      return toError(e);
    }
  }

  private byte executeMaybePacked() {
    return toIo(
        IO_RESOLVE_PACKED,

        toDecode(_PACKED),

        _CLOSE
    );
  }

  private byte executeNullObject() {
    setResult(EmptyObjectId.INSTANCE);

    return _FINALLY;
  }

  private byte executePacked() {
    parser = PackedParser.START;

    stringBuilder.setLength(0);

    return execute(_PARSE_PACKED);
  }

  private byte executeParsePacked() {
    boolean found;
    found = false;

    String id;
    id = "";

    IOException exception;
    exception = null;

    outer: //
    while (decoderBuffer.hasRemaining()) {
      char c;
      c = decoderBuffer.get();

      switch (parser) {
        case ERROR:
          exception = new IOException("Failed to parse packed-refs");

          break outer;
        case HEADER:
          if (c == Git.LF) {
            parser = PackedParser.ID;
          }

          break;
        case ID:
          if (c == Git.SP) {
            id = stringBuilder.toString();

            stringBuilder.setLength(0);

            parser = PackedParser.NAME;
          }

          else if (Git.isHexDigit(c)) {
            stringBuilder.append(c);
          }

          else {
            parser = PackedParser.ERROR;
          }

          break;
        case NAME:
          if (c == Git.LF) {
            String name;
            name = stringBuilder.toString();

            if (ref.matches(name)) {
              found = true;

              break outer;
            }

            else {
              stringBuilder.setLength(0);

              parser = PackedParser.START;
            }
          }

          else {
            stringBuilder.append(c);
          }

          break;
        case START:
          if (c == '#') {
            parser = PackedParser.HEADER;
          }

          else if (Git.isHexDigit(c)) {
            stringBuilder.append(c);

            parser = PackedParser.ID;
          }

          else {
            parser = PackedParser.ERROR;
          }

          break;
        default:
          throw new UnsupportedOperationException("Implement me @ " + parser);
      }
    }

    if (exception != null) {
      return toError(exception);
    }

    if (found) {
      try {
        ObjectId objectId;
        objectId = ObjectId.parse(id);

        setResult(objectId);

        return _CLOSE;
      } catch (InvalidObjectIdFormatException e) {
        return toError(e);
      }
    } else {
      return toIo(
          IO_READ,

          toDecode(_PARSE_PACKED),

          _CLOSE
      );
    }
  }

  private void ioClose() {
    close(channel);

    channel = null;
  }

  private void ioRead() throws IOException {
    if (channelBuffer.position() > 0) {
      channelBuffer.compact();
    }

    int thisReadCount;
    thisReadCount = channel.read(channelBuffer);

    if (thisReadCount > 0) {
      channelReadCount += thisReadCount;
    }

    channelBuffer.flip();
  }

  private void ioResolveLoose() throws IOException {
    ResolvedPath maybeFile;
    maybeFile = repository.resolveLoose(ref);

    maybeFile.acceptPathNameVisitor(ResolveLoose.INSTANCE, this);
  }

  private void ioResolvePacked() throws IOException {
    ResolvedPath maybeFile;
    maybeFile = repository.resolvePackedRefs();

    maybeFile.acceptPathNameVisitor(ResolvePacked.INSTANCE, this);
  }

  private boolean isChannelEof() {
    return channelReadCount >= channelReadLimit;
  }

  private void setResult(MaybeObjectId result) {
    log(ESUCCESS, result);

    super.setResult(result);
  }

  private byte toDecode(byte onReady) {
    decodeReady = onReady;

    return _DECODE;
  }

  private enum PackedParser {

    ERROR,

    HEADER,

    ID,

    NAME,

    START

  }

  private static class ResolveLoose extends SimplePathNameVisitor {
    static final ResolveLoose INSTANCE = new ResolveLoose();

    @Override
    public final Void visitRegularFile(RegularFile file, ResolveRef p) throws IOException {
      p.openFile(file);

      return null;
    }

    @Override
    protected final Void defaultAction(PathName pathName, ResolveRef p) throws IOException {
      p.ioReady(_MAYBE_PACKED);

      return null;
    }
  }

  private static class ResolvePacked extends SimplePathNameVisitor {
    static final ResolvePacked INSTANCE = new ResolvePacked();

    @Override
    public final Void visitRegularFile(RegularFile file, ResolveRef p) throws IOException {
      p.openFile(file);

      return null;
    }

    @Override
    protected final Void defaultAction(PathName pathName, ResolveRef p) throws IOException {
      p.ioReady(_NULL_OBJECT);

      return null;
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy