Package org.gradle.gradleplugin.foundation

Source Code of org.gradle.gradleplugin.foundation.GradlePluginLord$CommandLineArgumentAlteringNotification

/*
* Copyright 2010 the original author or authors.
*
* 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 org.gradle.gradleplugin.foundation;

import org.codehaus.groovy.runtime.StackTraceUtils;
import org.gradle.StartParameter;
import org.gradle.api.LocationAwareException;
import org.gradle.api.internal.DefaultClassPathProvider;
import org.gradle.api.logging.LogLevel;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.configuration.ImplicitTasksConfigurer;
import org.gradle.foundation.CommandLineAssistant;
import org.gradle.foundation.ProjectView;
import org.gradle.foundation.TaskView;
import org.gradle.foundation.common.ObserverLord;
import org.gradle.foundation.ipc.basic.ProcessLauncherServer;
import org.gradle.foundation.queue.ExecutionQueue;
import org.gradle.gradleplugin.foundation.favorites.FavoriteTask;
import org.gradle.gradleplugin.foundation.favorites.FavoritesEditor;
import org.gradle.gradleplugin.foundation.request.ExecutionRequest;
import org.gradle.gradleplugin.foundation.request.RefreshTaskListRequest;
import org.gradle.gradleplugin.foundation.request.Request;
import org.gradle.util.GUtil;

import java.io.File;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;

/**
* This class has nothing to do with plugins inside of gradle, but are related to making a plugin that uses gradle, such
* as for an IDE. It is also used by the standalone IDE (that way the standalone UI and plugin UIs are kept in synch).
* <p/> This is the class that stores most of the information that the Gradle plugin works directly with. It is meant to
* simplify creating a plugin that uses gradle. It maintains a queue of commands to execute and executes them in a
* separate process due to some complexities with gradle and its dependencies classpaths and potential memory issues.
*
* @author mhunsicker
*/
public class GradlePluginLord {
    private final Logger logger = Logging.getLogger(GradlePluginLord.class);

    private File gradleHomeDirectory;   //the directory where gradle is installed
    private File currentDirectory;      //the directory of your gradle-based project
    private File customGradleExecutor;  //probably will be null. This allows a user to specify a different batch file or shell script to initiate gradle.

    private List<ProjectView> projects = new ArrayList<ProjectView>();

    private FavoritesEditor favoritesEditor;  //an editor for the current favorites. The user can edit this at any time, hence we're using an editor.

    private ExecutionQueue<Request> executionQueue;
    private LinkedBlockingQueue<Request> currentlyExecutingRequests = new LinkedBlockingQueue<Request>();

    private boolean isStarted;  //this flag is mostly to prevent initialization from firing off repeated refresh requests.

    private StartParameter.ShowStacktrace stackTraceLevel = StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS;
    private LogLevel logLevel = LogLevel.LIFECYCLE;

    private ObserverLord<GeneralPluginObserver> generalObserverLord = new ObserverLord<GeneralPluginObserver>();
    private ObserverLord<RequestObserver> requestObserverLord = new ObserverLord<RequestObserver>();
    private ObserverLord<SettingsObserver> settingsObserverLord = new ObserverLord<SettingsObserver>();

    private ObserverLord<CommandLineArgumentAlteringListener> commandLineArgumentObserverLord = new ObserverLord<CommandLineArgumentAlteringListener>();

    private long nextRequestID = 1//a unique number assigned to requests

    public List<ProjectView> getProjects() {
        return Collections.unmodifiableList(projects);
    }

   /**
    * Sets the current projects. This is only supposed to be called by internal gradle classes.
    * @param newProjects
    */
    public void setProjects(final List<ProjectView> newProjects) {
        projects.clear();
        if (newProjects != null) {
           projects.addAll(newProjects);
        }

      generalObserverLord.notifyObservers(new ObserverLord.ObserverNotification<GeneralPluginObserver>() {
            public void notify(GeneralPluginObserver observer) {
                observer.projectsAndTasksReloaded(newProjects != null);
            }
        });
    }

   public interface GeneralPluginObserver {
        /**
        * Notification that we're about to reload the projects and tasks.
        */
        public void startingProjectsAndTasksReload();

        /**
         * Notification that the projects and tasks have been reloaded. You may want
         * to repopulate or update your views.
         * @param wasSuccessful true if they were successfully reloaded. False if an error occurred so we no longer can show the projects and tasks (probably an error in a .gradle file).
        */
        public void projectsAndTasksReloaded(boolean wasSuccessful);
    }

   public interface RequestObserver
   {
      public void executionRequestAdded( ExecutionRequest request );

      public void refreshRequestAdded( RefreshTaskListRequest request );

        /**
         * Notification that a command is about to be executed. This is mostly useful
         * for IDE's that may need to save their files.
        */
        public void aboutToExecuteRequest( Request request );

      /**
       * Notification that the command has completed execution.
       * @param request the original request containing the command that was executed
       * @param result the result of the command
       * @param output the output from gradle executing the command
       */
      public void requestExecutionComplete( Request request, int result, String output );
   }

   public interface SettingsObserver {

       /**
        * Notification that some settings have changed for the plugin. Settings such as current directory, gradle home
        * directory, etc. This is useful for UIs that need to update their UIs when this is changed by other means.
        */
       public void settingsChanged();
   }



    public GradlePluginLord() {
        favoritesEditor = new FavoritesEditor();

        //create the queue that executes the commands. The contents of this interaction are where we actually launch gradle.
        executionQueue = new ExecutionQueue<Request>(new ExecutionQueueInteraction());

        currentDirectory = new File(System.getProperty("user.dir"));

        String gradleHomeProperty = System.getProperty("gradle.home");
        if (gradleHomeProperty != null) {
           gradleHomeDirectory = new File(gradleHomeProperty);
        }
        else {
           gradleHomeDirectory = new DefaultClassPathProvider().getGradleHome();
        }
    }


    public File getGradleHomeDirectory() {
        return gradleHomeDirectory;
    }

   /**
    * sets the gradle home directory. You can't just set this here. You must also set the "gradle.home" system property.
    * This code could do this for you, but at this time, I didn't want this to have side effects and setting "gradle.home"
    * can affect other things and there may be some timing issues.
    * @param gradleHomeDirectory the new home directory
    */
    public void setGradleHomeDirectory(File gradleHomeDirectory) {
        if( areEqual( this.gradleHomeDirectory, gradleHomeDirectory ) )    //already set to this. This prevents recursive notifications.
        {
            return;
        }
       this.gradleHomeDirectory = gradleHomeDirectory;
        notifySettingsChanged();
    }

    /**
    * @return the root directory of your gradle project.
    */
    public File getCurrentDirectory() {
        return currentDirectory;
    }

    /**
    * @param  currentDirectory the new root directory of your gradle project.
    * @returns true if we changed the current directory, false if not (it was already set to this)
    */
    public boolean setCurrentDirectory(File currentDirectory) {
        if( areEqual( this.currentDirectory, currentDirectory ) )    //already set to this. This prevents recursive notifications.
        {
            return false;
        }
        this.currentDirectory = currentDirectory;
        notifySettingsChanged();
        return true;
    }

    public File getCustomGradleExecutor() {
        return customGradleExecutor;
    }

    public boolean setCustomGradleExecutor(File customGradleExecutor) {
        if( areEqual( this.customGradleExecutor, customGradleExecutor ) )    //already set to this. This prevents recursive notifications.
        {
            return false;
        }
        this.customGradleExecutor = customGradleExecutor;
        notifySettingsChanged();
        return true;
    }


    public FavoritesEditor getFavoritesEditor() {
        return favoritesEditor;
    }

   /**
    * this allows you to change how much information is given when an error occurs.
    */
    public void setStackTraceLevel(StartParameter.ShowStacktrace stackTraceLevel) {
        if( areEqual( this.stackTraceLevel, stackTraceLevel ) )    //already set to this. This prevents recursive notifications.
        {
            return;
        }
       this.stackTraceLevel = stackTraceLevel;
        notifySettingsChanged();
    }

    public StartParameter.ShowStacktrace getStackTraceLevel() {
        return stackTraceLevel;
    }

    public LogLevel getLogLevel() {
        return logLevel;
    }

    public void setLogLevel(LogLevel logLevel) {
        if (logLevel == null) {
           return;
        }

       if( areEqual( this.logLevel, logLevel ) )    //already set to this. This prevents recursive notifications.
       {
           return;
       }
       this.logLevel = logLevel;
       notifySettingsChanged();
    }

    /**
     * Call this to start execution. This is done after you've initialized everything.
    */
    public void startExecutionQueue() {
        isStarted = true;
    }


    /**
      * This gives requests of the queue and then executes them by kicking off gradle
      * in a separate process. Most of the code here is tedious setup code needed to
      * start the server. The server is what starts gradle and opens a socket for
      * interprocess communication so we can receive messages back from gradle.
    */
    private class ExecutionQueueInteraction implements ExecutionQueue.ExecutionInteraction<Request> {
        /**
         * When this is called, execute the given request.
         *
         * @param  request    the request to execute.
        */
        public void execute( final Request request) {

            //mark this request as being currently executed
            currentlyExecutingRequests.add( request );

            notifyAboutToExecuteRequest( request );

           //I'm just putting these in temp variables for easier debugging
            File currentDirectory = getCurrentDirectory();
            File gradleHomeDirectory = getGradleHomeDirectory();
            File customGradleExecutor = getCustomGradleExecutor();

            //the protocol handles the command line to launch gradle and messaging between us and said externally launched gradle.
            ProcessLauncherServer.Protocol serverProtocol = request.createServerProtocol(logLevel, stackTraceLevel, currentDirectory, gradleHomeDirectory, customGradleExecutor);

            //the server kicks off gradle as an external process and manages the communication with said process
            ProcessLauncherServer server = new ProcessLauncherServer(serverProtocol);
            request.setProcessLauncherServer(server);

            //we need to know when this command is finished executing so we can mark it as complete and notify any observers.
            server.addServerObserver( new ProcessLauncherServer.ServerObserver()
            {
               public void clientExited( int result, String output )
               {
                  currentlyExecutingRequests.remove( request );
                  notifyRequestExecutionComplete( request, result, output );
               }

               public void serverExited() { }
            }, false );

            server.start();
        }
    }

    private void notifyAboutToExecuteRequest( final Request request )
    {
      requestObserverLord.notifyObservers( new ObserverLord.ObserverNotification<RequestObserver>()
      {
          public void notify( RequestObserver observer )
          {
             try { //wrap this in a try/catch block so exceptions in the observer doesn't stop everything
                observer.aboutToExecuteRequest( request );
             }
             catch( Exception e )
             {
                logger.error( "notifying aboutToExecuteCommand() " + e.getMessage() );
             }
          }
       } );
    }

   private void notifyRequestExecutionComplete( final Request request, final int result, final String output )
   {
      requestObserverLord.notifyObservers( new ObserverLord.ObserverNotification<RequestObserver>()
      {
          public void notify( RequestObserver observer )
          {
             try { //wrap this in a try/catch block so exceptions in the observer doesn't stop everything
                observer.requestExecutionComplete( request, result, output );
             }
             catch( Exception e )
             {
                logger.error( "notifying requestExecutionComplete() " + e.getMessage() );
             }
          }
       } );
   }

    /**
     * Adds an observer for various events. See PluginObserver.
     *
     * @param  observer     your observer
     * @param  inEventQueue true if you want to be notified in the Event Dispatch Thread.
    */
    public void addGeneralPluginObserver(GeneralPluginObserver observer, boolean inEventQueue) {
        generalObserverLord.addObserver(observer, inEventQueue);
    }

    public void removeGeneralPluginObserver(GeneralPluginObserver observer) {
        generalObserverLord.removeObserver(observer);
    }

    public void addRequestObserver( RequestObserver observer, boolean inEventQueue )
    {
       requestObserverLord.addObserver( observer, inEventQueue );
    }

    public void removeRequestObserver( RequestObserver observer )
    {
       requestObserverLord.removeObserver( observer );
    }

    public void addSettingsObserver( SettingsObserver observer, boolean inEventQueue )
    {
       settingsObserverLord.addObserver( observer, inEventQueue );
    }

    public void removeSettingsObserver( SettingsObserver observer )
    {
       settingsObserverLord.removeObserver( observer );
    }

    private void notifySettingsChanged() {
        settingsObserverLord.notifyObservers(new ObserverLord.ObserverNotification<SettingsObserver>() {
            public void notify(SettingsObserver observer) {
                observer.settingsChanged();
            }
        });
    }

    /**
     * Determines if two are objects are equal and considers them both being null as equal
     * @param object1 the first object
     * @param object2 the second object
     * @return true if they're both null or both equal.
     */
   private boolean areEqual( Object object1, Object object2)
   {
      if ( object1 == null || object2 == null )
      {
         return object2 == object1; //yes, we're not using '.equals', we're making sure they both equal null because one of them is null!
      }

      return object1.equals(object2);
   }

    /**
     * Determines if all required setup is complete based on the current settings.
     *
     * @return true if a setup is complete, false if not.
    */
    public boolean isSetupComplete() {
        //return gradleWrapper.getGradleHomeDirectory() != null &&
        //       gradleWrapper.getGradleHomeDirectory().exists() &&
        return getCurrentDirectory() != null &&
                getCurrentDirectory().exists();
    }

   public Request addExecutionRequestToQueue( String fullCommandLine, String displayName )
   {
      return addExecutionRequestToQueue( fullCommandLine, displayName, false );
   }

   /**
     * This executes a task in a background thread. This creates or uses
     * an existing OutputPanel to display the results.
     *
     * @param  task       the task to execute.
     * @param forceOutputToBeShown overrides the user setting onlyShowOutputOnErrors so that the output is shown regardless
    */
    public Request addExecutionRequestToQueue(final TaskView task, boolean forceOutputToBeShown, String... additionCommandLineOptions)
    {
        if( task == null ) {
           return null;
        }

       String fullCommandLine = CommandLineAssistant.appendAdditionalCommandLineOptions(task, additionCommandLineOptions);
        return addExecutionRequestToQueue(fullCommandLine, task.getFullTaskName(), forceOutputToBeShown );
    }

   /**
    * This executes all the tasks together in a background thread. That is, all tasks
    * are passed to a single gradle call at once. This creates or uses an existing
    * OutputPanel to display the results.
    *
    * @param tasks the tasks to execute
    * @param forceOutputToBeShown overrides the user setting onlyShowOutputOnErrors so that the output is shown regardless
    * @param additionCommandLineOptions additional command line options to exeucte.
    */
    public Request addExecutionRequestToQueue(final List<TaskView> tasks, boolean forceOutputToBeShown, String... additionCommandLineOptions) {

      if( tasks == null || tasks.isEmpty() ) {
         return null;
      }

      if( tasks.size() == 1 ) { //if there's only 1, just treat it as one
         return addExecutionRequestToQueue( tasks.get( 0 ), forceOutputToBeShown, additionCommandLineOptions );
      }

      String singleCommandLine = CommandLineAssistant.combineTasks( tasks, additionCommandLineOptions );
      return addExecutionRequestToQueue(singleCommandLine, tasks.get( 0 ).getName() + "...", forceOutputToBeShown );
    }

    /**
     * Executes several favorites commands at once as a single command. This has the affect
     * of simply concatenating all the favorite command lines into a single line.
     * @param favorites a list of favorites. If just one favorite, it executes it normally.
     *                  If multiple favorites, it executes them all at once as a single command.
     */
    public Request addExecutionRequestToQueue(List<FavoriteTask> favorites) {
        if( favorites.isEmpty() ) {
            return null;
        }

        FavoriteTask firstFavoriteTask = favorites.get( 0 );
        String displayName;
        String fullCommandLine;
        boolean alwaysShowOutput = firstFavoriteTask.alwaysShowOutput();

        if( favorites.size() == 1 )
        {
            displayName = firstFavoriteTask.getDisplayName();
            fullCommandLine = firstFavoriteTask.getFullCommandLine();
        }
        else
        {
            displayName = "Multiple (" + firstFavoriteTask.getDisplayName() + ", ... )";
            fullCommandLine = FavoritesEditor.combineFavoriteCommandLines(favorites);
        }

        return addExecutionRequestToQueue( fullCommandLine, displayName, alwaysShowOutput );
    }

   /**
    * Call this to execute a task in a background thread. This creates or uses
    * an existing OutputPanel to display the results. This version takes text
    * instead of a task object.
    *
    * @param  fullCommandLine the full command line to pass to gradle.
    * @param  displayName     what we show on the tab.
    * @param forceOutputToBeShown overrides the user setting onlyShowOutputOnErrors so that the output is shown regardless
    */
    public Request addExecutionRequestToQueue(String fullCommandLine, String displayName, boolean forceOutputToBeShown) {
        if (!isStarted) {
           return null;
        }

      if (fullCommandLine == null){
           return null;
        }

      //here we'll give the UI a chance to add things to the command line.
        fullCommandLine = alterCommandLine(fullCommandLine);

        final ExecutionRequest request = new ExecutionRequest( getNextRequestID(), fullCommandLine, displayName, forceOutputToBeShown, executionQueue );
        requestObserverLord.notifyObservers( new ObserverLord.ObserverNotification<RequestObserver>()
        {
           public void notify( RequestObserver observer )
           {
              observer.executionRequestAdded( request );
           }
        } );
        executionQueue.addRequestToQueue(request);
        return request;
    }

    private synchronized long getNextRequestID()
    {
       return nextRequestID++;
    }

    /**
     * This will refresh the project/task tree.
     * @return the Request that was created. Null if no request created.
     */
    public Request addRefreshRequestToQueue() {
        return addRefreshRequestToQueue( null );
    }

    /**
     * This will refresh the project/task tree. This version allows you to specify additional
     * arguments to be passed to gradle during the refresh (such as -b to specify a build file)
     * @param additionalCommandLineArguments the arguments to add, or null if none.
     * @return the Request that was created. Null if no request created.
     */
    public Request addRefreshRequestToQueue( String additionalCommandLineArguments ) {
        if (!isStarted){
           return null;
        }

       if( hasRequestOfType( RefreshTaskListRequest.TYPE ) )
       {
          return null; //we're already doing a refresh.
       }

       //we'll request a task list since there is no way to do a no op. We're not really interested
        //in what's being executed, just the ability to get the task list (which must be populated as
        //part of executing anything).
        String fullCommandLine = ImplicitTasksConfigurer.TASKS_TASK;

        if( additionalCommandLineArguments != null )
        {
            fullCommandLine += ' ' + additionalCommandLineArguments;
        }

        //here we'll give the UI a chance to add things to the command line.
        fullCommandLine = alterCommandLine(fullCommandLine);

        final RefreshTaskListRequest request = new RefreshTaskListRequest( getNextRequestID(), fullCommandLine, executionQueue, this);
        executionQueue.addRequestToQueue(request);
        // TODO - fix this race condition - request may already have completed
        requestObserverLord.notifyObservers( new ObserverLord.ObserverNotification<RequestObserver>()
        {
           public void notify( RequestObserver observer )
           {
              observer.refreshRequestAdded( request );
           }
        } );
        return request;
    }

    /**
     * This is where we notify listeners and give them a chance to add things to the command line.
     *
     * @param  fullCommandLine the full command line
     *
     * @return the new command line.
    */
    private String alterCommandLine(String fullCommandLine) {
        CommandLineArgumentAlteringNotification notification = new CommandLineArgumentAlteringNotification(fullCommandLine);
        commandLineArgumentObserverLord.notifyObservers(notification);

        return notification.getFullCommandLine();
    }


    //
    /**
     * This class notifies the listeners and modifies the command line by adding
     * additional commands to it. Each listener will be given the 'new' full command
     * line, so the order you add things becomes important.
    */
    private class CommandLineArgumentAlteringNotification implements ObserverLord.ObserverNotification<CommandLineArgumentAlteringListener> {
        private StringBuilder fullCommandLineBuilder;

        private CommandLineArgumentAlteringNotification(String fullCommandLine) {
            this.fullCommandLineBuilder = new StringBuilder(fullCommandLine);
        }

        public void notify(CommandLineArgumentAlteringListener observer) {
            String additions = observer.getAdditionalCommandLineArguments(fullCommandLineBuilder.toString());
            if (additions != null) {
               fullCommandLineBuilder.append(' ').append(additions);
            }
        }

        public String getFullCommandLine() {
            return fullCommandLineBuilder.toString();
        }
    }


    /**
     * This allows you to add a listener that can add additional command line
     * arguments whenever gradle is executed. This is useful if you've customized
     * your gradle build and need to specify, for example, an init script.
     *
     * param  listener   the listener that modifies the command line arguments.
    */
    public void addCommandLineArgumentAlteringListener(CommandLineArgumentAlteringListener listener) {
        commandLineArgumentObserverLord.addObserver(listener, false);
    }

    public void removeCommandLineArgumentAlteringListener(CommandLineArgumentAlteringListener listener) {
        commandLineArgumentObserverLord.removeObserver(listener);
    }

   /**
    * This code was copied from BuildExceptionReporter.reportBuildFailure in gradle's source, then modified slightly
    * to compensate for the fact that we're not driven by options or logging things to a logger object.
    */
    public static String getGradleExceptionMessage(Throwable failure, StartParameter.ShowStacktrace stackTraceLevel) {
        if (failure == null) {
           return "";
        }

      Formatter formatter = new Formatter();

        formatter.format("%nBuild failed.%n");

        if (stackTraceLevel == StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS) {
           formatter.format("Use the stack trace options to get more details.");
        }

      if (failure != null) {
            formatter.format("%n");

            if (failure instanceof LocationAwareException) {
                LocationAwareException scriptException = (LocationAwareException) failure;
                formatter.format("%s%n%n", scriptException.getLocation());
                formatter.format("%s", scriptException.getOriginalMessage());

                for (Throwable cause : scriptException.getReportableCauses()) {
                    formatter.format("%nCause: %s", getMessage(cause));
                }
            } else {
                formatter.format("%s", getMessage(failure));
            }

            if (stackTraceLevel != StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS) {
                formatter.format("%n%nException is:\n");
                if (stackTraceLevel == StartParameter.ShowStacktrace.ALWAYS_FULL) {
                   return formatter.toString() + getStackTraceAsText(failure);
                }

               return formatter.toString() + getStackTraceAsText(StackTraceUtils.deepSanitize(failure));
            }
        }

        return formatter.toString();
    }

    private static String getStackTraceAsText(Throwable t) {
        StringBuilder builder = new StringBuilder();
        StackTraceElement[] stackTraceElements = t.getStackTrace();

        for (int index = 0; index < stackTraceElements.length; index++) {
            StackTraceElement stackTraceElement = stackTraceElements[index];
            builder.append("   ").append(stackTraceElement.toString()).append('\n');
        }

        return builder.toString();
    }

    //tries to get a message from a Throwable. Something there's a message and sometimes there's not.
    private static String getMessage(Throwable throwable) {
        String message = throwable.getMessage();
        if (!GUtil.isTrue(message)){
           message = String.format("%s (no error message)", throwable.getClass().getName());
        }

       if (throwable.getCause() != null){
           message += "\nCaused by: " + getMessage(throwable.getCause());
        }

       return message;
    }

   /**
    * Determines if there are tasks executing or waiting to execute.
    * We only care about execution requests, not refresh requests.
    * @return true if this is busy, false if not.
    */
   public boolean isBusy()
   {
      return hasRequestOfType( ExecutionRequest.TYPE );
   }

   /**
    Determines if we have an request of the specified type
    @param type the sought type of request.
    @return true if it has the request, false if not.
    */
   private boolean hasRequestOfType( Request.Type type )
   {
      Iterator<Request> iterator = currentlyExecutingRequests.iterator();
      while( iterator.hasNext() )
      {
         ExecutionQueue.Request request = iterator.next();
         if( request.getType() == type )
         {
            return true;
         }
      }

      List<Request> pendingRequests = executionQueue.getRequests();
      iterator = pendingRequests.iterator();
      while( iterator.hasNext() )
      {
         ExecutionQueue.Request request = iterator.next();
         if( request.getType() == type )
         {
            return true;
         }
      }

      return false;
   }
}
TOP

Related Classes of org.gradle.gradleplugin.foundation.GradlePluginLord$CommandLineArgumentAlteringNotification

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.