// Path: src/main/java/com/google/jenkins/plugins/storage/util/StorageUtil.java
// public class StorageUtil {
// 
//   /**
//    * Compute the relative path of the given file inclusion, relative to the given workspace. If the
//    * path is absolute, it returns the root-relative path instead.
//    *
//    * @param include The file whose relative path we are computing.
//    * @param workspace The workspace containing the included file.
//    * @return The unix-style relative path of file.
//    * @throws UploadException when the input is malformed
//    */
//   public static String getRelative(FilePath include, FilePath workspace) throws UploadException {
//     LinkedList<String> segments = new LinkedList<String>();
//     while (!include.equals(workspace)) {
//       segments.push(include.getName());
//       include = include.getParent();
//       if (Strings.isNullOrEmpty(include.getName())) {
//         // When we reach "/" we're done either way.
//         break;
//       }
//     }
//     return String.join("/", segments);
//   }
// 
//   /**
//    * If a path prefix to strip has been specified, and the input string starts with that prefix,
//    * returns the portion of the input after that prefix. Otherwise, returns the unmodified input.
//    *
//    * @param filename Input string to strip.
//    * @param pathPrefix Name of the path prefix to strip on.
//    * @return Remaining portion of the input after prefix is stripped.
//    */
//   public static String getStrippedFilename(String filename, String pathPrefix) {
//     if (pathPrefix != null && filename != null && filename.startsWith(pathPrefix)) {
//       return filename.substring(pathPrefix.length());
//     }
//     return filename;
//   }
// 
//   /**
//    * Perform variable expansion for non-pipeline steps.
//    *
//    * @param name The name, potentially including with variables
//    * @param run The current run, used to determine pipeline status and to get environment.
//    * @param listener Task listener, used to get environment
//    * @return The updated name, with variables resolved
//    * @throws InterruptedException If getting the environment of the run throws an
//    *     InterruptedException.
//    * @throws IOException If getting the environment of the run throws an IOException.
//    */
//   public static String replaceMacro(String name, Run<?, ?> run, TaskListener listener)
//       throws InterruptedException, IOException {
//     if (run instanceof AbstractBuild) {
//       name = Util.replaceMacro(name, run.getEnvironment(listener));
//     }
//     return name;
//   }
// 
//   /**
//    * Look up credentials by name.
//    *
//    * @param credentials The name of the credentials to look up.
//    * @return The corresponding {@link GoogleRobotCredentials}.
//    * @throws AbortException If credentials not found.
//    */
//   public static GoogleRobotCredentials lookupCredentials(String credentials) throws AbortException {
//     GoogleRobotCredentials result = GoogleRobotCredentials.getById(credentials);
//     if (result == null) {
//       throw new AbortException("Unknown credentials: " + credentials);
//     }
//     return result;
//   }
// }


import com.google.jenkins.plugins.storage.util.StorageUtil;
import com.google.jenkins.plugins.util.Resolve;
import hudson.Extension;
import hudson.FilePath;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.util.FormValidation;
import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;
import javax.annotation.Nullable;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
/*
 * Copyright 2013 Google LLC
 *
 * 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
 *
 *     https://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 com.google.jenkins.plugins.storage;


/**
 * This upload extension implements the classical upload pattern where a user provides an Ant-style
 * glob, e.g. ** / *.java relative to the build workspace, and those files are uploaded to the
 * storage bucket.
 */
public class ClassicUpload extends AbstractUpload {

  /**
   * Construct the classic upload implementation from the base properties and the glob for matching
   * files.
   *
   * @param bucket GCS bucket to upload files to.
   * @param module Helper class for connecting to the GCS API.
   * @param pattern The glob of files to upload, which potentially contains unresolved symbols, such
   *     as $JOB_NAME and $BUILD_NUMBER.
   * @param bucketNameWithVars Deprecated format for bucket.
   * @param sourceGlobWithVars Deprecated. Old name kept for deserialization.
   */
  @DataBoundConstructor
  public ClassicUpload(
      String bucket,
      @Nullable UploadModule module,
      String pattern,
      // Legacy arguments for backwards compatibility
      @Deprecated @Nullable String bucketNameWithVars,
      @Deprecated @Nullable String sourceGlobWithVars) {
    super(bucket != null ? bucket : bucketNameWithVars, module);
    this.sourceGlobWithVars = pattern != null ? pattern : sourceGlobWithVars;
    Objects.requireNonNull(this.sourceGlobWithVars);
  }

  /** {@inheritDoc} */
  @Override
  public String getDetails() {
    return getPattern();
  }

  /** {@inheritDoc} */
  @Override
  @Nullable
  protected UploadSpec getInclusions(Run<?, ?> run, FilePath workspace, TaskListener listener)
      throws UploadException {
    try {

// Path: src/main/java/com/google/jenkins/plugins/storage/util/StorageUtil.java
// public class StorageUtil {
// 
//   /**
//    * Compute the relative path of the given file inclusion, relative to the given workspace. If the
//    * path is absolute, it returns the root-relative path instead.
//    *
//    * @param include The file whose relative path we are computing.
//    * @param workspace The workspace containing the included file.
//    * @return The unix-style relative path of file.
//    * @throws UploadException when the input is malformed
//    */
//   public static String getRelative(FilePath include, FilePath workspace) throws UploadException {
//     LinkedList<String> segments = new LinkedList<String>();
//     while (!include.equals(workspace)) {
//       segments.push(include.getName());
//       include = include.getParent();
//       if (Strings.isNullOrEmpty(include.getName())) {
//         // When we reach "/" we're done either way.
//         break;
//       }
//     }
//     return String.join("/", segments);
//   }
// 
//   /**
//    * If a path prefix to strip has been specified, and the input string starts with that prefix,
//    * returns the portion of the input after that prefix. Otherwise, returns the unmodified input.
//    *
//    * @param filename Input string to strip.
//    * @param pathPrefix Name of the path prefix to strip on.
//    * @return Remaining portion of the input after prefix is stripped.
//    */
//   public static String getStrippedFilename(String filename, String pathPrefix) {
//     if (pathPrefix != null && filename != null && filename.startsWith(pathPrefix)) {
//       return filename.substring(pathPrefix.length());
//     }
//     return filename;
//   }
// 
//   /**
//    * Perform variable expansion for non-pipeline steps.
//    *
//    * @param name The name, potentially including with variables
//    * @param run The current run, used to determine pipeline status and to get environment.
//    * @param listener Task listener, used to get environment
//    * @return The updated name, with variables resolved
//    * @throws InterruptedException If getting the environment of the run throws an
//    *     InterruptedException.
//    * @throws IOException If getting the environment of the run throws an IOException.
//    */
//   public static String replaceMacro(String name, Run<?, ?> run, TaskListener listener)
//       throws InterruptedException, IOException {
//     if (run instanceof AbstractBuild) {
//       name = Util.replaceMacro(name, run.getEnvironment(listener));
//     }
//     return name;
//   }
// 
//   /**
//    * Look up credentials by name.
//    *
//    * @param credentials The name of the credentials to look up.
//    * @return The corresponding {@link GoogleRobotCredentials}.
//    * @throws AbortException If credentials not found.
//    */
//   public static GoogleRobotCredentials lookupCredentials(String credentials) throws AbortException {
//     GoogleRobotCredentials result = GoogleRobotCredentials.getById(credentials);
//     if (result == null) {
//       throw new AbortException("Unknown credentials: " + credentials);
//     }
//     return result;
//   }
// }


// Path: src/main/java/com/google/jenkins/plugins/storage/ClassicUpload.java
import com.google.jenkins.plugins.storage.util.StorageUtil;
import com.google.jenkins.plugins.util.Resolve;
import hudson.Extension;
import hudson.FilePath;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.util.FormValidation;
import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;
import javax.annotation.Nullable;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;

/*
 * Copyright 2013 Google LLC
 *
 * 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
 *
 *     https://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 com.google.jenkins.plugins.storage;


/**
 * This upload extension implements the classical upload pattern where a user provides an Ant-style
 * glob, e.g. ** / *.java relative to the build workspace, and those files are uploaded to the
 * storage bucket.
 */
public class ClassicUpload extends AbstractUpload {

  /**
   * Construct the classic upload implementation from the base properties and the glob for matching
   * files.
   *
   * @param bucket GCS bucket to upload files to.
   * @param module Helper class for connecting to the GCS API.
   * @param pattern The glob of files to upload, which potentially contains unresolved symbols, such
   *     as $JOB_NAME and $BUILD_NUMBER.
   * @param bucketNameWithVars Deprecated format for bucket.
   * @param sourceGlobWithVars Deprecated. Old name kept for deserialization.
   */
  @DataBoundConstructor
  public ClassicUpload(
      String bucket,
      @Nullable UploadModule module,
      String pattern,
      // Legacy arguments for backwards compatibility
      @Deprecated @Nullable String bucketNameWithVars,
      @Deprecated @Nullable String sourceGlobWithVars) {
    super(bucket != null ? bucket : bucketNameWithVars, module);
    this.sourceGlobWithVars = pattern != null ? pattern : sourceGlobWithVars;
    Objects.requireNonNull(this.sourceGlobWithVars);
  }

  /** {@inheritDoc} */
  @Override
  public String getDetails() {
    return getPattern();
  }

  /** {@inheritDoc} */
  @Override
  @Nullable
  protected UploadSpec getInclusions(Run<?, ?> run, FilePath workspace, TaskListener listener)
      throws UploadException {
    try {

      String globResolvedVars = StorageUtil.replaceMacro(getPattern(), run, listener);