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

br.com.objectos.git.MutableTree 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.collections.list.ImmutableList;
import br.com.objectos.collections.list.MutableList;
import br.com.objectos.core.io.Write;
import br.com.objectos.core.object.Checks;
import br.com.objectos.core.object.ToString;
import br.com.objectos.fs.Directory;
import br.com.objectos.fs.PathNameVisitor;
import br.com.objectos.fs.RegularFile;
import br.com.objectos.fs.ResolvedPath;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.util.zip.Deflater;

/**
 * A class for creating a Git tree hierarchy suitable for recursively writing to
 * a repository.
 *
 * @since 2
 */
public final class MutableTree extends MutableTreeEntry {

  private static final ThisSelector SELECTOR = new ThisSelector();

  private byte[] contents;

  private final MutableList entries = MutableList.create();

  private final String name;

  private ObjectId objectId;

  /**
   * Creates an unnamed {@code MutableTree} instance.
   *
   * @since 3
   */
  public MutableTree() {
    name = "";
  }

  /**
   * Creates a {@code MutableTree} instance having the specified name.
   *
   * @param name
   *        the name of this tree when serving as a tree entry
   *
   * @throws IllegalArgumentException
   *         if name is an empty string
   *
   * @since 3
   */
  public MutableTree(String name) {
    this.name = Checks.checkNotNull(name, "name == null");

    Checks.checkArgument(!this.name.isEmpty(), "name must not be empty");
  }

  /**
   * Adds the specified entry to this mutable tree.
   *
   * @param entry
   *        the entry to be added
   */
  public final void addEntry(Entry entry) {
    entries.addWithNullMessage(entry, "entry == null");
  }

  /**
   * Adds the specified blob to this mutable tree.
   *
   * @param blob
   *        the blob to be added
   */
  public final void addEntry(MutableBlob blob) {
    entries.addWithNullMessage(blob, "blob == null");
  }

  /**
   * Adds the specified tree to this mutable tree.
   *
   * @param tree
   *        the tree to be added
   */
  public final void addEntry(MutableTree tree) {
    Checks.checkArgument(tree.isNamed(), "MutableTree must define a name to serve as an entry");

    entries.addWithNullMessage(tree, "tree == null");
  }

  /**
   * Removes all of the entries from this tree.
   */
  public final void clear() {
    entries.clear();
  }

  @Override
  public final void formatToString(StringBuilder sb, int depth) {
    ToString.formatStart(sb, this);

    formatToStringImpl(null, sb, depth);

    sb.append('\n');

    ToString.formatEnd(sb, depth);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final EntryMode getMode() {
    return EntryMode.TREE;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final String getName() {
    return name;
  }

  final void addEntry(MutableTreeEntry entry) {
    entries.addWithNullMessage(entry, "entry == null");
  }

  final void computeContents(GitInjector injector, Charset charset) throws IOException {
    ByteArrayWriter body;
    body = injector.getByteArrayWriter();

    ByteArrayWriter contents;
    contents = injector.getByteArrayWriter();

    try {
      computeContents0(injector, charset, body, contents);
    } finally {
      injector.putByteArrayWriter(body);

      injector.putByteArrayWriter(contents);
    }
  }

  @Override
  final ObjectId computeObjectId(GitInjector injector, Charset charset) throws IOException {
    computeContents(injector, charset);

    return objectId;
  }

  @Override
  final void executeWriteTree(WriteTree wt) {
    ImmutableList sorted;
    sorted = entries.toImmutableSortedList(ORDER);

    for (int i = sorted.size() - 1; i >= 0; i--) {
      MutableTreeEntry entry;
      entry = sorted.get(i);

      entry.executeWriteTree(wt);
    }

    wt.writeMutableTree(sorted);
  }

  @Override
  final void formatToStringImpl(String prefix, StringBuilder sb, int depth) {
    sb.append('\n');

    ToString.appendIndentation(sb, depth);

    EntryMode mode;
    mode = EntryMode.TREE;

    sb.append(mode.printMode());

    sb.append(Git.SP);

    sb.append(mode.printType());

    sb.append(Git.SP);

    sb.append("0000000000000000000000000000000000000000");

    sb.append("    ");

    String newPrefix;

    if (prefix != null) {
      newPrefix = prefix + '/' + name;
    } else {
      newPrefix = name;
    }

    sb.append(newPrefix);

    for (int i = 0, size = entries.size(); i < size; i++) {
      MutableTreeEntry e;
      e = entries.get(i);

      e.formatToStringImpl(newPrefix, sb, depth);
    }
  }

  final ObjectId getObjectId() {
    return objectId;
  }

  final boolean isNamed() {
    return !name.equals("");
  }

  final IllegalArgumentException validate(StringBuilder sb) {
    IllegalArgumentException result;
    result = null;

    if (isNamed()) {
      validate0(sb, "tree is named");
    }

    if (entries.isEmpty()) {
      validate0(sb, "tree has no entries");
    }

    if (sb.length() > 0) {
      String message;
      message = sb.toString();

      sb.setLength(0);

      result = new IllegalArgumentException(message);
    }

    return result;
  }

  @Override
  final void writeTree(
      GitInjector injector, Repository repository) throws GitStubException, IOException {
    ResolvedPath resolved;
    resolved = repository.resolveLooseObject(objectId);

    ResolvedPath maybe;
    maybe = resolved.acceptPathNameVisitor(SELECTOR, null);

    if (maybe == null) {
      return;
    }

    Write.byteArray(maybe, contents);

    for (int i = 0, size = entries.size(); i < size; i++) {
      MutableTreeEntry entry;
      entry = entries.get(i);

      entry.writeTree(injector, repository);
    }
  }

  private void computeContents0(
      GitInjector injector, Charset charset, ByteArrayWriter body, ByteArrayWriter contents)
      throws IOException {
    entries.sort(MutableTreeEntry.ORDER);

    for (int i = 0, size = entries.size(); i < size; i++) {
      MutableTreeEntry entry;
      entry = entries.get(i);

      EntryMode mode;
      mode = entry.getMode();

      body.write(mode.getBytes());

      body.write(Git.UTF8__space);

      String name;
      name = entry.getName();

      byte[] nameBytes;
      nameBytes = name.getBytes(charset);

      body.write(nameBytes);

      body.write(Git.NULL);

      ObjectId id;
      id = entry.computeObjectId(injector, charset);

      body.write(id.getBytes());
    }

    byte[] prefix;
    prefix = ObjectKind.TREE.headerPrefix;

    contents.write(prefix);

    String bodySize;
    bodySize = Integer.toString(body.size(), 10);

    byte[] objectSizeBytes;
    objectSizeBytes = bodySize.getBytes(charset);

    contents.write(objectSizeBytes);

    contents.write(Git.NULL);

    contents.write(body);

    MessageDigest messageDigest;
    messageDigest = injector.getMessageDigest(Git.SHA1);

    objectId = contents.computeObjectId(messageDigest);

    Deflater deflater;
    deflater = injector.getDeflater();

    ByteArrayWriter compressed;
    compressed = body.clear();

    contents.deflate(deflater, compressed);

    injector.putDeflater(deflater);

    this.contents = compressed.toByteArray();
  }

  private void validate0(StringBuilder sb, String reason) {
    if (sb.length() == 0) {
      sb.append("Refuse to write tree for the following reason(s): ");
    } else {
      sb.append(", ");
    }

    sb.append(reason);
  }

  private static class ThisSelector implements PathNameVisitor {

    @Override
    public final ResolvedPath visitDirectory(Directory directory, Void p) {
      throw new UnsupportedOperationException("Implement me");
    }

    @Override
    public final ResolvedPath visitNotFound(ResolvedPath notFound, Void p) throws IOException {
      return notFound.createParents();
    }

    @Override
    public final ResolvedPath visitRegularFile(RegularFile file, Void p) {
      // do nothing, tree exists
      return null;
    }

  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy