/*
 * Copyright 2011 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.api.internal.artifacts.ivyservice.projectmodule;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.UncheckedExecutionException;
import org.gradle.api.artifacts.ModuleVersionIdentifier;
import org.gradle.api.artifacts.component.BuildIdentifier;
import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
import org.gradle.api.internal.artifacts.ImmutableModuleIdentifierFactory;
import org.gradle.api.internal.artifacts.Module;
import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.LocalComponentMetadataBuilder;
import org.gradle.api.internal.attributes.AttributesSchemaInternal;
import org.gradle.api.internal.project.ProjectInternal;
import org.gradle.api.internal.project.ProjectRegistry;
import org.gradle.api.internal.project.ProjectState;
import org.gradle.api.internal.project.ProjectStateRegistry;
import org.gradle.internal.Factory;
import org.gradle.internal.UncheckedException;
import org.gradle.internal.component.local.model.DefaultLocalComponentMetadata;
import org.gradle.internal.component.local.model.LocalComponentMetadata;

import javax.annotation.Nullable;
import java.util.concurrent.ExecutionException;

/**
 * Provides the metadata for a component consumed from the same build that produces it.
 *
 * Currently, the metadata for a component is different based on whether it is consumed from the producing build or from another build. This difference should go away.
 */
public class DefaultProjectLocalComponentProvider implements LocalComponentProvider {
    private final ProjectStateRegistry projectStateRegistry;
    private final ProjectRegistry<ProjectInternal> projectRegistry;
    private final LocalComponentMetadataBuilder metadataBuilder;
    private final ImmutableModuleIdentifierFactory moduleIdentifierFactory;
    private final BuildIdentifier thisBuild;
    private final LoadingCache<ProjectComponentIdentifier, LocalComponentMetadata> projects = CacheBuilder.newBuilder().build(new CacheLoader<ProjectComponentIdentifier, LocalComponentMetadata>() {
        @Override
        public LocalComponentMetadata load(ProjectComponentIdentifier projectIdentifier) {
            return getLocalComponentMetadata(projectIdentifier);
        }
    });

    public DefaultProjectLocalComponentProvider(ProjectStateRegistry projectStateRegistry, ProjectRegistry<ProjectInternal> projectRegistry, LocalComponentMetadataBuilder metadataBuilder, ImmutableModuleIdentifierFactory moduleIdentifierFactory, BuildIdentifier thisBuild) {
        this.projectStateRegistry = projectStateRegistry;
        this.projectRegistry = projectRegistry;
        this.metadataBuilder = metadataBuilder;
        this.moduleIdentifierFactory = moduleIdentifierFactory;
        this.thisBuild = thisBuild;
    }

    public LocalComponentMetadata getComponent(ProjectComponentIdentifier projectIdentifier) {
        if (!isLocalProject(projectIdentifier)) {
            return null;
        }
        try {
            return projects.get(projectIdentifier);
        } catch (ExecutionException e) {
            throw UncheckedException.throwAsUncheckedException(e.getCause());
        } catch (UncheckedExecutionException e) {
            throw UncheckedException.throwAsUncheckedException(e.getCause());
        }
    }

    private boolean isLocalProject(ProjectComponentIdentifier projectIdentifier) {
        return projectIdentifier.getBuild().equals(thisBuild);
    }

    private LocalComponentMetadata getLocalComponentMetadata(ProjectComponentIdentifier projectIdentifier) {
        // TODO - the project model should be reachable from ProjectState without another lookup
        final ProjectInternal project = projectRegistry.getProject(projectIdentifier.getProjectPath());
        if (project == null) {
            throw new IllegalArgumentException(projectIdentifier + " not found.");
        }
        final ProjectState projectState = projectStateRegistry.stateFor(project);
        return projectState.withMutableState(new Factory<LocalComponentMetadata>() {
            @Nullable
            @Override
            public LocalComponentMetadata create() {
                return getLocalComponentMetadata(projectState, project);
            }
        });
    }

    private LocalComponentMetadata getLocalComponentMetadata(ProjectState projectState, ProjectInternal project) {
        Module module = project.getModule();
        ModuleVersionIdentifier moduleVersionIdentifier = moduleIdentifierFactory.moduleWithVersion(module.getGroup(), module.getName(), module.getVersion());
        ProjectComponentIdentifier componentIdentifier = projectState.getComponentIdentifier();
        DefaultLocalComponentMetadata metaData = new DefaultLocalComponentMetadata(moduleVersionIdentifier, componentIdentifier, module.getStatus(), (AttributesSchemaInternal) project.getDependencies().getAttributesSchema());
        for (ConfigurationInternal configuration : project.getConfigurations().withType(ConfigurationInternal.class)) {
            metadataBuilder.addConfiguration(metaData, configuration);
        }
        return metaData;
    }

}

@@ -714,23 +714,16 @@
 he.Cache
-Builder
 ;%0Aimport
@@ -756,59 +756,15 @@
 ache
-Loader;%0Aimport com.google.common.cache.LoadingCache
+Builder
 ;%0Aim
@@ -1887,56 +1887,8 @@
 ble;
-%0Aimport java.util.concurrent.ExecutionException;
 %0A%0A/*
@@ -2575,15 +2575,8 @@
 nal 
-Loading
 Cach
@@ -2676,269 +2676,8 @@
 ild(
-new CacheLoader%3CProjectComponentIdentifier, LocalComponentMetadata%3E() %7B%0A        @Override%0A        public LocalComponentMetadata load(ProjectComponentIdentifier projectIdentifier) %7B%0A            return getLocalComponentMetadata(projectIdentifier);%0A        %7D%0A    %7D
 );%0A%0A
@@ -3422,165 +3422,898 @@
     
-return projects.get(projectIdentifier);%0A        %7D catch (ExecutionException e) %7B%0A            throw UncheckedException.throwAsUncheckedException(e.getCause())
+// Resolving a project component can cause traversal to other projects, at which%0A            // point we could release the project lock and allow another task to run.  We can't%0A            // use a cache loader here because it is synchronized.  If the other task also tries%0A            // to resolve a project component, he can block trying to get the lock around the%0A            // loader while still holding the project lock.  To avoid this deadlock, we check,%0A            // then release the project lock only if we need to resolve the project and ensure%0A            // that only the thread holding the lock can populate the metadata for a project.%0A            LocalComponentMetadata metadata = projects.getIfPresent(projectIdentifier);%0A            if (metadata == null) %7B%0A                metadata = getLocalComponentMetadata(projectIdentifier);%0A            %7D%0A            return metadata
 ;%0A  
@@ -4665,24 +4665,30 @@
 entMetadata(
+final 
 ProjectCompo
@@ -5332,63 +5332,317 @@
     
-return getLocalComponentMetadata(projectState, project)
+LocalComponentMetadata metadata = projects.getIfPresent(projectIdentifier);%0A                if (metadata == null) %7B%0A                    metadata = getLocalComponentMetadata(projectState, project);%0A                    projects.put(projectIdentifier, metadata);%0A                %7D%0A                return metadata
 ;%0A  

Remove deadlock due to locking during cache entry initialization