initial import
This commit is contained in:
Родитель
e59265aea5
Коммит
02a5b5d1b8
|
@ -0,0 +1,12 @@
|
|||
bin
|
||||
obj
|
||||
build
|
||||
out
|
||||
libs
|
||||
.gradle
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
*~
|
||||
*.user
|
||||
gradle.properties
|
|
@ -0,0 +1,16 @@
|
|||
apply plugin: 'idea'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
ivy { url '../repository' }
|
||||
ivy { url 'http://unity-technologies.github.com/kaizen/repositories/integration' }
|
||||
}
|
||||
dependencies {
|
||||
classpath group: 'kaizen', name: 'kaizen', version: 'latest.integration'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kaizen-bundle'
|
||||
|
||||
version = '1.0'
|
||||
|
Двоичный файл не отображается.
|
@ -0,0 +1,6 @@
|
|||
#Thu Sep 27 09:13:56 BRT 2012
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=http\://services.gradle.org/distributions/gradle-1.2-bin.zip
|
|
@ -0,0 +1,164 @@
|
|||
#!/bin/bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/"
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED"
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
|
@ -0,0 +1,90 @@
|
|||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
|
@ -0,0 +1,15 @@
|
|||
include 'CodeEditor.Composition'
|
||||
|
||||
//include 'CodeEditor.Text'
|
||||
//include 'CodeEditor.Collections'
|
||||
//include 'CodeEditor.UI.UnityEditor'
|
||||
|
||||
rootProject.children.each { project ->
|
||||
def projectDirName = "src/$project.name"
|
||||
|
||||
project.projectDir = new File(settingsDir, projectDirName)
|
||||
assert project.projectDir.isDirectory()
|
||||
|
||||
project.buildFileName = "${project.name}.gradle"
|
||||
assert project.buildFile.isFile()
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>9.0.21022</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{2ED73522-E34D-4CAB-8A42-6421FB9B1E77}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>CodeEditor.Composition.Tests</RootNamespace>
|
||||
<AssemblyName>CodeEditor.Composition.Tests</AssemblyName>
|
||||
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<FileUpgradeFlags>
|
||||
</FileUpgradeFlags>
|
||||
<OldToolsVersion>3.5</OldToolsVersion>
|
||||
<UpgradeBackupLocation />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="nunit.framework, Version=2.6.0.12051, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\..\..\NUnit\bin\nunit.framework.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
</Reference>
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CompositionContainerTest.cs" />
|
||||
<Compile Include="LazyImportManyTest.cs" />
|
||||
<Compile Include="ImportDefinitionProviderTest.cs" />
|
||||
<Compile Include="LazyImportTest.cs" />
|
||||
<Compile Include="LazyTest.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Frameworks\CodeEditor.Composition\CodeEditor.Composition.csproj">
|
||||
<Project>{9DB8BFD3-06C8-4A8C-8842-5931B924B56C}</Project>
|
||||
<Name>CodeEditor.Composition</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
|
@ -0,0 +1,4 @@
|
|||
dependencies {
|
||||
editor project(path: ':CodeEditor.Composition', configuration: 'editor')
|
||||
editor group: 'nunit', name: 'nunit.framework', version: '2.6.0.12051'
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
using CodeEditor.Composition.Hosting;
|
||||
using CodeEditor.Composition.Primitives;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CodeEditor.Composition.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class CompositionContainerTest
|
||||
{
|
||||
private CompositionContainer _container;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_container = new CompositionContainer(GetType().Assembly);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ContainerIsExportedAsExportProvider()
|
||||
{
|
||||
Assert.AreSame(_container, GetExportedValue<IExportProvider>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetExportedValueAlwaysReturnSameValue()
|
||||
{
|
||||
var service = GetExportedValue<AService>();
|
||||
Assert.IsNotNull(service);
|
||||
Assert.AreSame(service, GetExportedValue<AService>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetExportedValueSatisfiesPropertyImports()
|
||||
{
|
||||
Assert.AreSame(GetExportedValue<AService>(), GetExportedValue<AServiceWithDependencies>().AService);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetExportedValueSatisfiesFieldImports()
|
||||
{
|
||||
Assert.AreSame(GetExportedValue<AService>(), GetExportedValue<AServiceWithFieldDependencies>().AService);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetExportedValueSatisfiesInterfaceImportImplementedByInternalType()
|
||||
{
|
||||
Assert.AreSame(GetExportedValue<IService>(), GetExportedValue<IServiceWithDependencies>().AService);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetExportedValueSatisfiesImportingConstructor()
|
||||
{
|
||||
Assert.AreSame(GetExportedValue<IService>(), GetExportedValue<ServiceWithImportingConstructor>().AService);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PartCanExportMultipleContracts()
|
||||
{
|
||||
Assert.AreSame(GetExportedValue<IContract1>(), GetExportedValue<IContract2>());
|
||||
Assert.IsTrue(GetExportedValue<IContract1>() is PartWithMultipleContracts);
|
||||
}
|
||||
|
||||
private T GetExportedValue<T>()
|
||||
{
|
||||
return _container.GetExportedValue<T>();
|
||||
}
|
||||
|
||||
// ReSharper disable ClassNeverInstantiated.Global
|
||||
[Export]
|
||||
public class AService
|
||||
{
|
||||
}
|
||||
|
||||
[Export]
|
||||
public class AServiceWithDependencies
|
||||
{
|
||||
[Import]
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Global
|
||||
public AService AService { get; set; }
|
||||
// ReSharper restore UnusedAutoPropertyAccessor.Global
|
||||
}
|
||||
|
||||
[Export]
|
||||
public class AServiceWithFieldDependencies
|
||||
{
|
||||
[Import]
|
||||
public AService AService;
|
||||
}
|
||||
|
||||
public interface IServiceWithDependencies
|
||||
{
|
||||
IService AService { get; }
|
||||
}
|
||||
|
||||
public interface IService
|
||||
{
|
||||
}
|
||||
|
||||
// ReSharper disable UnusedMember.Global
|
||||
[Export(typeof(IService))]
|
||||
internal class ServiceImpl : IService
|
||||
{
|
||||
}
|
||||
|
||||
[Export(typeof(IServiceWithDependencies))]
|
||||
internal class ServiceWithDependenciesImpl : IServiceWithDependencies
|
||||
{
|
||||
[Import]
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Global
|
||||
public IService AService { get; set; }
|
||||
// ReSharper restore UnusedAutoPropertyAccessor.Global
|
||||
}
|
||||
// ReSharper restore UnusedMember.Global
|
||||
|
||||
[Export]
|
||||
public class ServiceWithImportingConstructor
|
||||
{
|
||||
[ImportingConstructor]
|
||||
ServiceWithImportingConstructor(IService service)
|
||||
{
|
||||
AService = service;
|
||||
}
|
||||
|
||||
public IService AService { get; private set; }
|
||||
}
|
||||
// ReSharper restore ClassNeverInstantiated.Global
|
||||
|
||||
public interface IContract1
|
||||
{
|
||||
}
|
||||
|
||||
public interface IContract2
|
||||
{
|
||||
}
|
||||
|
||||
[Export(typeof(IContract1))]
|
||||
[Export(typeof(IContract2))]
|
||||
public class PartWithMultipleContracts : IContract1, IContract2
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
using System.Linq;
|
||||
using CodeEditor.Composition.Primitives;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CodeEditor.Composition.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class ImportDefinitionProviderTest
|
||||
{
|
||||
[Test]
|
||||
public void LazyImport()
|
||||
{
|
||||
var provider = new ImportDefinitionProvider();
|
||||
var import = provider.ImportsFor(typeof(ClassWithLazyImport)).Single();
|
||||
Assert.AreEqual(typeof(IService), import.ContractType);
|
||||
Assert.AreEqual(ImportCardinality.One, import.Cardinality);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LazyImportMany()
|
||||
{
|
||||
var provider = new ImportDefinitionProvider();
|
||||
var import = provider.ImportsFor(typeof(ClassWithLazyImportMany)).Single();
|
||||
Assert.AreEqual(typeof(IService), import.ContractType);
|
||||
Assert.AreEqual(ImportCardinality.Many, import.Cardinality);
|
||||
}
|
||||
|
||||
public class ClassWithLazyImport
|
||||
{
|
||||
[Import]
|
||||
public Lazy<IService, IServiceMetadata> Service;
|
||||
}
|
||||
|
||||
public class ClassWithLazyImportMany
|
||||
{
|
||||
[ImportMany]
|
||||
public Lazy<IService, IServiceMetadata>[] Services;
|
||||
}
|
||||
|
||||
public interface IService
|
||||
{
|
||||
}
|
||||
|
||||
public interface IServiceMetadata
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
using System.Linq;
|
||||
using CodeEditor.Composition.Hosting;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CodeEditor.Composition.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class LazyImportManyTest
|
||||
{
|
||||
[Test]
|
||||
public void OnlyServicesWithMatchingMetadataAreProvided()
|
||||
{
|
||||
var container = new CompositionContainer(GetType().Assembly);
|
||||
|
||||
var service = container.GetExportedValue<ServiceWithImports>();
|
||||
Assert.IsNotNull(service.Imports);
|
||||
|
||||
var expected = new[]
|
||||
{
|
||||
new {Type = typeof(Service1), Name = "Foo"},
|
||||
new {Type = typeof(Service2), Name = "Bar"}
|
||||
};
|
||||
var actual = service.Imports
|
||||
.Select(import => new {Type = import.Value.GetType(), import.Metadata.Name});
|
||||
|
||||
CollectionAssert.AreEquivalent(expected, actual.ToArray());
|
||||
}
|
||||
|
||||
[Export]
|
||||
// ReSharper disable ClassNeverInstantiated.Local
|
||||
class ServiceWithImports
|
||||
{
|
||||
[ImportMany]
|
||||
#pragma warning disable 649
|
||||
public Lazy<IService, IServiceMetadata>[] Imports;
|
||||
#pragma warning restore 649
|
||||
}
|
||||
|
||||
interface IService
|
||||
{
|
||||
}
|
||||
|
||||
[ExportServiceWithMetadata("Foo")]
|
||||
class Service1 : IService
|
||||
{
|
||||
}
|
||||
|
||||
[ExportServiceWithMetadata("Bar")]
|
||||
class Service2 : IService
|
||||
{
|
||||
}
|
||||
|
||||
[Export(typeof(IService))]
|
||||
// ReSharper disable UnusedMember.Local
|
||||
class ServiceWithoutMetadata : IService
|
||||
// ReSharper restore UnusedMember.Local
|
||||
{
|
||||
}
|
||||
// ReSharper restore ClassNeverInstantiated.Local
|
||||
|
||||
interface IServiceMetadata
|
||||
{
|
||||
string Name { get; }
|
||||
}
|
||||
|
||||
class ExportServiceWithMetadata : ExportAttribute, IServiceMetadata
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
|
||||
public ExportServiceWithMetadata(string name) : base(typeof(IService))
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
using CodeEditor.Composition.Hosting;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CodeEditor.Composition.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class LazyImportTest
|
||||
{
|
||||
[Test]
|
||||
public void MetadataIsProvided()
|
||||
{
|
||||
var container = new CompositionContainer(GetType().Assembly);
|
||||
|
||||
var service = container.GetExportedValue<ServiceWithLazyImport>();
|
||||
|
||||
Assert.IsNotNull(service.Import);
|
||||
Assert.AreEqual(42, service.Import.Metadata.Value);
|
||||
|
||||
Assert.IsNotNull(service.Import.Value);
|
||||
Assert.AreSame(service.Import.Value, service.Import.Value);
|
||||
}
|
||||
|
||||
[Export]
|
||||
public class ServiceWithLazyImport
|
||||
{
|
||||
[Import]
|
||||
public Lazy<ServiceWithMetadata, IServiceMetadata> Import;
|
||||
}
|
||||
|
||||
[ExportWithMetadata(42)]
|
||||
public class ServiceWithMetadata
|
||||
{
|
||||
}
|
||||
|
||||
public interface IServiceMetadata
|
||||
{
|
||||
int Value { get; }
|
||||
}
|
||||
|
||||
class ExportWithMetadataAttribute : ExportAttribute, IServiceMetadata
|
||||
{
|
||||
public int Value { get; private set; }
|
||||
|
||||
public ExportWithMetadataAttribute(int value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using NUnit.Framework;
|
||||
|
||||
namespace CodeEditor.Composition.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class LazyTest
|
||||
{
|
||||
[Test]
|
||||
public void FactoryIsInvokedOnlyOnce()
|
||||
{
|
||||
var count = 0;
|
||||
var lazy = new Lazy<object>(() => (object) ++count);
|
||||
Assert.AreEqual(0, count);
|
||||
|
||||
Assert.AreEqual(1, lazy.Value);
|
||||
Assert.AreEqual(1, count);
|
||||
|
||||
Assert.AreEqual(1, lazy.Value);
|
||||
Assert.AreEqual(1, count);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("CodeEditor.Composition.Tests")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
@ -0,0 +1,77 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>9.0.30729</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{9DB8BFD3-06C8-4A8C-8842-5931B924B56C}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>CodeEditor.Composition</RootNamespace>
|
||||
<AssemblyName>CodeEditor.Composition</AssemblyName>
|
||||
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<OldToolsVersion>3.5</OldToolsVersion>
|
||||
<SolutionDir Condition="'$(SolutionDir)' == ''">..\..\..\</SolutionDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
</Reference>
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CompositionError.cs" />
|
||||
<Compile Include="CompositionException.cs" />
|
||||
<Compile Include="Hosting\AssemblyExtensions.cs" />
|
||||
<Compile Include="Hosting\DirectoryCatalog.cs" />
|
||||
<Compile Include="ICompositionContainer.cs" />
|
||||
<Compile Include="Lazy.cs" />
|
||||
<Compile Include="Hosting\AggregateCatalog.cs" />
|
||||
<Compile Include="Hosting\AssemblyCatalog.cs" />
|
||||
<Compile Include="Hosting\CompositionContainer.cs" />
|
||||
<Compile Include="ContractAttribute.cs" />
|
||||
<Compile Include="ExportAttribute.cs" />
|
||||
<Compile Include="Primitives\CustomAttribute.cs" />
|
||||
<Compile Include="ImportManyAttribute.cs" />
|
||||
<Compile Include="Primitives\Export.cs" />
|
||||
<Compile Include="Primitives\ExportDefinition.cs" />
|
||||
<Compile Include="Primitives\IExportDefinitionProvider.cs" />
|
||||
<Compile Include="Primitives\IMetadataProvider.cs" />
|
||||
<Compile Include="Primitives\ImportDefinition.cs" />
|
||||
<Compile Include="ImportAttribute.cs" />
|
||||
<Compile Include="ImportingConstructor.cs" />
|
||||
<Compile Include="Primitives\ImportCardinality.cs" />
|
||||
<Compile Include="Primitives\ImportDefinitionProvider.cs" />
|
||||
<Compile Include="Primitives\TypeExtensions.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
|
@ -0,0 +1,2 @@
|
|||
version = '1.0'
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
|
||||
namespace CodeEditor.Composition
|
||||
{
|
||||
public class CompositionError
|
||||
{
|
||||
public CompositionError(Type contractType, string message)
|
||||
{
|
||||
ContractType = contractType;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public Type ContractType { get; private set; }
|
||||
|
||||
public string Message { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CodeEditor.Composition
|
||||
{
|
||||
public class CompositionException : Exception
|
||||
{
|
||||
private readonly IList<CompositionError> _errors = new List<CompositionError>();
|
||||
|
||||
public CompositionException(CompositionError error) : base(error.Message)
|
||||
{
|
||||
Add(error);
|
||||
}
|
||||
|
||||
public CompositionException(Exception cause, CompositionError error) : base(error.Message, cause)
|
||||
{
|
||||
Add(error);
|
||||
}
|
||||
|
||||
public IList<CompositionError> Errors
|
||||
{
|
||||
get { return _errors; }
|
||||
}
|
||||
|
||||
private void Add(CompositionError error)
|
||||
{
|
||||
_errors.Add(error);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
|
||||
namespace CodeEditor.Composition
|
||||
{
|
||||
public abstract class ContractAttribute : Attribute
|
||||
{
|
||||
private readonly Type _contractType;
|
||||
|
||||
protected ContractAttribute(Type contractType)
|
||||
{
|
||||
_contractType = contractType;
|
||||
}
|
||||
|
||||
public Type ContractType
|
||||
{
|
||||
get { return _contractType; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
|
||||
namespace CodeEditor.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Marks a class as an export.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||
public class ExportAttribute : ContractAttribute
|
||||
{
|
||||
public ExportAttribute(Type contractType) : base(contractType) {}
|
||||
|
||||
public ExportAttribute() : base(null) {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CodeEditor.Composition.Primitives;
|
||||
|
||||
namespace CodeEditor.Composition.Hosting
|
||||
{
|
||||
public class AggregateCatalog : IExportDefinitionProvider
|
||||
{
|
||||
private readonly IExportDefinitionProvider[] _exportDefinitionProviders;
|
||||
|
||||
public AggregateCatalog(IEnumerable<IExportDefinitionProvider> exportProviders) : this(exportProviders.ToArray())
|
||||
{
|
||||
}
|
||||
|
||||
public AggregateCatalog(params IExportDefinitionProvider[] exportDefinitionProviders)
|
||||
{
|
||||
_exportDefinitionProviders = exportDefinitionProviders;
|
||||
}
|
||||
|
||||
public IEnumerable<ExportDefinition> GetExports(Type contractType)
|
||||
{
|
||||
return _exportDefinitionProviders.SelectMany(provider => provider.GetExports(contractType));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using CodeEditor.Composition.Primitives;
|
||||
|
||||
namespace CodeEditor.Composition.Hosting
|
||||
{
|
||||
public class AssemblyCatalog : IExportDefinitionProvider
|
||||
{
|
||||
public static IExportDefinitionProvider For(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
return new AggregateCatalog(AssemblyCatalogsFor(assemblies));
|
||||
}
|
||||
|
||||
private static IEnumerable<IExportDefinitionProvider> AssemblyCatalogsFor(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
return assemblies
|
||||
.Where(assembly => assembly.IsSafeForComposition())
|
||||
.Select(assembly => (IExportDefinitionProvider) new AssemblyCatalog(assembly));
|
||||
}
|
||||
|
||||
private Dictionary<Type, ExportDefinition[]> _exports;
|
||||
private readonly Assembly _assembly;
|
||||
|
||||
public AssemblyCatalog(Assembly assembly)
|
||||
{
|
||||
_assembly = assembly;
|
||||
}
|
||||
|
||||
public IEnumerable<ExportDefinition> GetExports(Type contractType)
|
||||
{
|
||||
ExportDefinition[] exportsDefinition;
|
||||
return Exports().TryGetValue(contractType, out exportsDefinition)
|
||||
? exportsDefinition
|
||||
: NoExportsDefinition;
|
||||
}
|
||||
|
||||
private Dictionary<Type, ExportDefinition[]> Exports()
|
||||
{
|
||||
lock (this)
|
||||
return _exports ?? (_exports = ComputeExports());
|
||||
}
|
||||
|
||||
private Dictionary<Type, ExportDefinition[]> ComputeExports()
|
||||
{
|
||||
return _assembly
|
||||
.GetTypes()
|
||||
.SelectMany<Type, ExportDefinition>(ExportsFromType)
|
||||
.Where(e => e != null)
|
||||
.GroupBy(e => e.ContractType)
|
||||
.ToDictionary(e => e.Key, e => e.ToArray());
|
||||
}
|
||||
|
||||
private IEnumerable<ExportDefinition> ExportsFromType(Type implementation)
|
||||
{
|
||||
return CustomAttribute<ExportAttribute>
|
||||
.AllFrom(implementation)
|
||||
.Select(attribute => new ExportDefinition(attribute.ContractType ?? implementation, implementation));
|
||||
}
|
||||
|
||||
private static readonly ExportDefinition[] NoExportsDefinition = new ExportDefinition[0];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace CodeEditor.Composition.Hosting
|
||||
{
|
||||
static class AssemblyExtensions
|
||||
{
|
||||
public static bool IsSafeForComposition(this Assembly assembly)
|
||||
{
|
||||
return CaresForComposition(assembly) && TypesAreAccessible(assembly);
|
||||
}
|
||||
|
||||
private static bool CaresForComposition(Assembly assembly)
|
||||
{
|
||||
return assembly
|
||||
.GetReferencedAssemblies()
|
||||
.Any(name => name.FullName.Equals(typeof(ExportAttribute).Assembly.FullName));
|
||||
}
|
||||
|
||||
private static bool TypesAreAccessible(Assembly assembly)
|
||||
{
|
||||
try
|
||||
{
|
||||
assembly.GetTypes();
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Trace.Write(string.Format("Error loading types from assembly `{0}': {1}", assembly, e));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using CodeEditor.Composition.Primitives;
|
||||
|
||||
namespace CodeEditor.Composition.Hosting
|
||||
{
|
||||
public class CompositionContainer : ICompositionContainer
|
||||
{
|
||||
private readonly Dictionary<Type, Export[]> _exports = new Dictionary<Type, Export[]>();
|
||||
private readonly Dictionary<Type, Lazy<object>> _parts = new Dictionary<Type, Lazy<object>>();
|
||||
private readonly IExportDefinitionProvider _exportDefinitionProvider;
|
||||
private readonly ImportDefinitionProvider _importDefinitionProvider = new ImportDefinitionProvider();
|
||||
|
||||
public CompositionContainer(Assembly assembly)
|
||||
: this(new AssemblyCatalog(assembly))
|
||||
{
|
||||
}
|
||||
|
||||
public CompositionContainer(params Assembly[] assemblies)
|
||||
: this(AssemblyCatalog.For(assemblies))
|
||||
{
|
||||
}
|
||||
|
||||
public CompositionContainer(IExportDefinitionProvider definitionProvider)
|
||||
{
|
||||
_exportDefinitionProvider = definitionProvider;
|
||||
AddExportedValue<IExportProvider>(this);
|
||||
}
|
||||
|
||||
public void AddExportedValue<T>(T value)
|
||||
{
|
||||
AddExport(new Export(new ExportDefinition(typeof(T), value.GetType()), () => value));
|
||||
}
|
||||
|
||||
private void AddExport(Export export)
|
||||
{
|
||||
_exports.Add(export.Definition.ContractType, new[] {export});
|
||||
}
|
||||
|
||||
public T GetExportedValue<T>()
|
||||
{
|
||||
return (T) GetExportedValue(typeof(T));
|
||||
}
|
||||
|
||||
public object GetExportedValue(Type contractType)
|
||||
{
|
||||
var export = GetExport(contractType);
|
||||
if (export == null)
|
||||
throw NoExportFoundError(contractType);
|
||||
return export.Value;
|
||||
}
|
||||
|
||||
private Export GetExport(Type contractType)
|
||||
{
|
||||
return GetExports(contractType).SingleOrDefault();
|
||||
}
|
||||
|
||||
public IEnumerable<Export> GetExports(Type contractType)
|
||||
{
|
||||
lock (_exports)
|
||||
return DoGetExports(contractType);
|
||||
}
|
||||
|
||||
private IEnumerable<Export> DoGetExports(Type contractType)
|
||||
{
|
||||
Export[] existing;
|
||||
if (_exports.TryGetValue(contractType, out existing))
|
||||
return existing;
|
||||
var exports = CreateExportsFor(contractType);
|
||||
_exports.Add(contractType, exports);
|
||||
return exports;
|
||||
}
|
||||
|
||||
private Export[] CreateExportsFor(Type contractType)
|
||||
{
|
||||
return _exportDefinitionProvider
|
||||
.GetExports(contractType)
|
||||
.Select(e => new Export(e, FactoryFor(e)))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private Func<object> FactoryFor(ExportDefinition exportDefinition)
|
||||
{
|
||||
return AccessorFor(exportDefinition, GetPartFor(exportDefinition.Implementation));
|
||||
}
|
||||
|
||||
private Lazy<object> GetPartFor(Type implementation)
|
||||
{
|
||||
lock (_parts)
|
||||
return DoGetPartFor(implementation);
|
||||
}
|
||||
|
||||
private Lazy<object> DoGetPartFor(Type implementation)
|
||||
{
|
||||
Lazy<object> existing;
|
||||
if (_parts.TryGetValue(implementation, out existing))
|
||||
return existing;
|
||||
var part = NewPartFor(implementation);
|
||||
_parts.Add(implementation, part);
|
||||
return part;
|
||||
}
|
||||
|
||||
private Lazy<object> NewPartFor(Type implementation)
|
||||
{
|
||||
return new Lazy<object>(() => InstantiatePart(implementation));
|
||||
}
|
||||
|
||||
private object InstantiatePart(Type implementation)
|
||||
{
|
||||
var importingConstructor = ImportingConstructorOf(implementation);
|
||||
var part = importingConstructor != null
|
||||
? CreateInstanceThrough(importingConstructor)
|
||||
: Activator.CreateInstance(implementation);
|
||||
ComposeParts(part);
|
||||
return part;
|
||||
}
|
||||
|
||||
private Func<object> AccessorFor(ExportDefinition exportDefinition, Lazy<object> part)
|
||||
{
|
||||
return () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return part.Value;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new CompositionException(e,
|
||||
new CompositionError(exportDefinition.ContractType, string.Format("Failed to create `{0}' to satisfy `{1}'!", exportDefinition.Implementation, exportDefinition.ContractType)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private object CreateInstanceThrough(ConstructorInfo importingConstructor)
|
||||
{
|
||||
return importingConstructor.Invoke(ExportedValuesFor(importingConstructor).ToArray());
|
||||
}
|
||||
|
||||
private IEnumerable<object> ExportedValuesFor(ConstructorInfo importingConstructor)
|
||||
{
|
||||
return importingConstructor.GetParameters().Select(p => GetExportedValue(p.ParameterType));
|
||||
}
|
||||
|
||||
private static ConstructorInfo ImportingConstructorOf(Type implementation)
|
||||
{
|
||||
return implementation.InstanceConstructors().SingleOrDefault(IsImportingConstructor);
|
||||
}
|
||||
|
||||
private static bool IsImportingConstructor(ConstructorInfo c)
|
||||
{
|
||||
return Attribute.IsDefined(c, typeof(ImportingConstructor));
|
||||
}
|
||||
|
||||
private void ComposeParts(object part)
|
||||
{
|
||||
foreach (var import in ImportsOf(part))
|
||||
Satisfy(import, part);
|
||||
}
|
||||
|
||||
private void Satisfy(ImportDefinition importDefinition, object part)
|
||||
{
|
||||
var exports = GetExportsSatisfying(importDefinition);
|
||||
Validate(importDefinition, exports);
|
||||
importDefinition.SatisfyWith(exports, part);
|
||||
}
|
||||
|
||||
private static void Validate(ImportDefinition importDefinition, Export[] exports)
|
||||
{
|
||||
switch (importDefinition.Cardinality)
|
||||
{
|
||||
case ImportCardinality.One:
|
||||
if (exports.Length == 0)
|
||||
throw NoExportFoundError(importDefinition.ContractType);
|
||||
if (exports.Length != 1)
|
||||
throw TooManyExportsError(importDefinition, exports);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static CompositionException TooManyExportsError(ImportDefinition importDefinition, Export[] exports)
|
||||
{
|
||||
return new CompositionException(
|
||||
new CompositionError(importDefinition.ContractType, string.Format("Too many exports for `{0}': `{1}'.", importDefinition.ContractType, exports.Select(e => e.Definition.Implementation.FullName).ToList())));
|
||||
}
|
||||
|
||||
private Export[] GetExportsSatisfying(ImportDefinition importDefinition)
|
||||
{
|
||||
return GetExports(importDefinition.ContractType)
|
||||
.Where(importDefinition.IsSatisfiableBy)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private IEnumerable<ImportDefinition> ImportsOf(object value)
|
||||
{
|
||||
return _importDefinitionProvider.ImportsFor(value.GetType());
|
||||
}
|
||||
|
||||
private static CompositionException NoExportFoundError(Type contractType)
|
||||
{
|
||||
return new CompositionException(
|
||||
new CompositionError(contractType, string.Format("Export `{0}' not found!", contractType)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using CodeEditor.Composition.Primitives;
|
||||
|
||||
namespace CodeEditor.Composition.Hosting
|
||||
{
|
||||
public class DirectoryCatalog : IExportDefinitionProvider
|
||||
{
|
||||
private readonly IExportDefinitionProvider _catalog;
|
||||
|
||||
public DirectoryCatalog(string directory)
|
||||
{
|
||||
_catalog = AssemblyCatalog.For(AllAssembliesIn(directory));
|
||||
}
|
||||
|
||||
public IEnumerable<ExportDefinition> GetExports(Type contractType)
|
||||
{
|
||||
return _catalog.GetExports(contractType);
|
||||
}
|
||||
|
||||
private static Assembly[] AllAssembliesIn(string directory)
|
||||
{
|
||||
return Directory.GetFiles(directory, "*.dll").Select(s => Assembly.LoadFrom(s)).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
using CodeEditor.Composition.Primitives;
|
||||
|
||||
namespace CodeEditor.Composition
|
||||
{
|
||||
public interface ICompositionContainer : IExportProvider
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using CodeEditor.Composition.Primitives;
|
||||
|
||||
namespace CodeEditor.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Imports a service.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
|
||||
public class ImportAttribute : ContractAttribute
|
||||
{
|
||||
public ImportAttribute(Type contractType) : base(contractType) {}
|
||||
public ImportAttribute() : base(null) {}
|
||||
|
||||
public virtual ImportCardinality Cardinality
|
||||
{
|
||||
get { return ImportCardinality.One; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using CodeEditor.Composition.Primitives;
|
||||
|
||||
namespace CodeEditor.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Imports all providers of a service.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
|
||||
public class ImportManyAttribute : ImportAttribute
|
||||
{
|
||||
public ImportManyAttribute(Type contractType) : base(contractType) { }
|
||||
public ImportManyAttribute() : base(null) { }
|
||||
|
||||
public override ImportCardinality Cardinality
|
||||
{
|
||||
get { return ImportCardinality.Many; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace CodeEditor.Composition
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Constructor)]
|
||||
public class ImportingConstructor : Attribute
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
|
||||
namespace CodeEditor.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// A lazy import to allow selecting the right service based on
|
||||
/// available metadata.
|
||||
///
|
||||
/// The service is only requested from the container when <see cref="Lazy{T}.Value"/>
|
||||
/// is called.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the contract type</typeparam>
|
||||
/// <typeparam name="TMetadata">the metadata type (must be compatible with a metadata attribute)</typeparam>
|
||||
public class Lazy<T, TMetadata> : Lazy<T> where T: class
|
||||
{
|
||||
private readonly TMetadata _metadata;
|
||||
|
||||
public Lazy(Func<T> valueFactory, TMetadata metadata) : base(valueFactory)
|
||||
{
|
||||
_metadata = metadata;
|
||||
}
|
||||
|
||||
public Lazy(Func<object> valueFactory, TMetadata metadata) : base(() => (T)valueFactory())
|
||||
{
|
||||
_metadata = metadata;
|
||||
}
|
||||
|
||||
public TMetadata Metadata
|
||||
{
|
||||
get { return _metadata; }
|
||||
}
|
||||
}
|
||||
|
||||
public class Lazy<T> where T : class
|
||||
{
|
||||
private Func<T> _valueFactory;
|
||||
private bool _hasValue;
|
||||
private T _value;
|
||||
|
||||
public Lazy(Func<T> valueFactory)
|
||||
{
|
||||
_valueFactory = valueFactory;
|
||||
}
|
||||
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (!_hasValue)
|
||||
{
|
||||
_value = _valueFactory();
|
||||
_valueFactory = null;
|
||||
_hasValue = true;
|
||||
}
|
||||
return _value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace CodeEditor.Composition.Primitives
|
||||
{
|
||||
static class CustomAttribute<T> where T : Attribute
|
||||
{
|
||||
public static T From(MemberInfo member)
|
||||
{
|
||||
return (T)Attribute.GetCustomAttribute(member, typeof(T));
|
||||
}
|
||||
|
||||
public static IEnumerable<T> AllFrom(MemberInfo member)
|
||||
{
|
||||
return Attribute.GetCustomAttributes(member, typeof(T)).Cast<T>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace CodeEditor.Composition.Primitives
|
||||
{
|
||||
public interface IExportProvider
|
||||
{
|
||||
IEnumerable<Export> GetExports(Type contractType);
|
||||
}
|
||||
|
||||
public static class ExportProviderExtensions
|
||||
{
|
||||
public static IEnumerable<Export> GetExportsWhereMetadata<T>(this IExportProvider provider, Func<T, bool> predicate, Type contractType)
|
||||
{
|
||||
return provider.GetExports(contractType).Where(e => e.Metadata.OfType<T>().Any(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
public class Export : Lazy<object>, IMetadataProvider
|
||||
{
|
||||
public Export(ExportDefinition definition, Func<object> valueFactory) : base(valueFactory)
|
||||
{
|
||||
Definition = definition;
|
||||
}
|
||||
|
||||
public ExportDefinition Definition
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public IEnumerable<object> Metadata
|
||||
{
|
||||
get { return Definition.Metadata; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CodeEditor.Composition.Primitives
|
||||
{
|
||||
public class ExportDefinition : IMetadataProvider
|
||||
{
|
||||
public ExportDefinition(Type contractType, Type definition)
|
||||
{
|
||||
ContractType = contractType;
|
||||
Implementation = definition;
|
||||
}
|
||||
|
||||
public Type ContractType { get; private set; }
|
||||
|
||||
public Type Implementation { get; private set; }
|
||||
|
||||
public IEnumerable<object> Metadata
|
||||
{
|
||||
get { return Attribute.GetCustomAttributes(Implementation); }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CodeEditor.Composition.Primitives
|
||||
{
|
||||
public interface IExportDefinitionProvider
|
||||
{
|
||||
IEnumerable<ExportDefinition> GetExports(Type contractType);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace CodeEditor.Composition.Primitives
|
||||
{
|
||||
public interface IMetadataProvider
|
||||
{
|
||||
IEnumerable<object> Metadata { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace CodeEditor.Composition.Primitives
|
||||
{
|
||||
public enum ImportCardinality
|
||||
{
|
||||
One,
|
||||
Many
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
|
||||
namespace CodeEditor.Composition.Primitives
|
||||
{
|
||||
public class ImportDefinition
|
||||
{
|
||||
private readonly Action<Export[], object> _action;
|
||||
private readonly Func<Export, bool> _constraint;
|
||||
|
||||
public ImportDefinition(Type contractType, ImportCardinality cardinality, Action<Export[], object> action, Func<Export, bool> constraint)
|
||||
{
|
||||
ContractType = contractType;
|
||||
Cardinality = cardinality;
|
||||
_action = action;
|
||||
_constraint = constraint;
|
||||
}
|
||||
|
||||
public Type ContractType { get; private set; }
|
||||
|
||||
public ImportCardinality Cardinality { get; private set; }
|
||||
|
||||
public void SatisfyWith(Export[] export, object target)
|
||||
{
|
||||
_action(export, target);
|
||||
}
|
||||
|
||||
public bool IsSatisfiableBy(Export export)
|
||||
{
|
||||
return _constraint(export);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace CodeEditor.Composition.Primitives
|
||||
{
|
||||
public class ImportDefinitionProvider
|
||||
{
|
||||
public IEnumerable<ImportDefinition> ImportsFor(Type type)
|
||||
{
|
||||
return type
|
||||
.InstanceMembers()
|
||||
.Select(m => new {Member = m, Attribute = ImportAttributeFrom(m)})
|
||||
.Where(m => m.Attribute != null)
|
||||
.Select(m => FromMember(m.Member, m.Attribute));
|
||||
}
|
||||
|
||||
private static ImportDefinition FromMember(MemberInfo member, ImportAttribute import)
|
||||
{
|
||||
var property = member as PropertyInfo;
|
||||
if (property != null)
|
||||
return FromProperty(property, import);
|
||||
|
||||
var field = member as FieldInfo;
|
||||
if (field != null)
|
||||
return FromField(field, import);
|
||||
|
||||
throw new CompositionException(new CompositionError(import.ContractType, string.Format("Unsupported import `{0}'.", member)));
|
||||
}
|
||||
|
||||
private static ImportDefinition FromField(FieldInfo fieldInfo, ImportAttribute import)
|
||||
{
|
||||
return ImportDefinitionFrom(import, fieldInfo.FieldType, fieldInfo.SetValue);
|
||||
}
|
||||
|
||||
private static ImportDefinition FromProperty(PropertyInfo p, ImportAttribute import)
|
||||
{
|
||||
return ImportDefinitionFrom(import, p.PropertyType, (part, value) => p.SetValue(part, value, null));
|
||||
}
|
||||
|
||||
private static ImportDefinition ImportDefinitionFrom(ImportAttribute import, Type actualType, Action<object, object> setter)
|
||||
{
|
||||
return new ImportDefinitionBuilder(import, actualType, setter).Build();
|
||||
}
|
||||
|
||||
private static ImportAttribute ImportAttributeFrom(MemberInfo member)
|
||||
{
|
||||
return CustomAttribute<ImportAttribute>.From(member);
|
||||
}
|
||||
}
|
||||
|
||||
internal class ImportDefinitionBuilder
|
||||
{
|
||||
private readonly ImportAttribute _import;
|
||||
private readonly Type _actualType;
|
||||
private readonly Action<object, object> _setter;
|
||||
private readonly bool _isLazyType;
|
||||
private readonly Type _contractType;
|
||||
private readonly Type _elementType;
|
||||
|
||||
public ImportDefinitionBuilder(ImportAttribute import, Type actualType, Action<object, object> setter)
|
||||
{
|
||||
_import = import;
|
||||
_actualType = actualType;
|
||||
_setter = setter;
|
||||
_elementType = ElementType();
|
||||
_isLazyType = IsLazyType(_elementType);
|
||||
_contractType = ContractType();
|
||||
}
|
||||
|
||||
public ImportDefinition Build()
|
||||
{
|
||||
return new ImportDefinition(_contractType, Cardinality, BuildSetter(), Constraint());
|
||||
}
|
||||
|
||||
private Func<Export, bool> Constraint()
|
||||
{
|
||||
if (_isLazyType)
|
||||
return MetadataConstraintFor(MetadataType);
|
||||
return _ => true;
|
||||
}
|
||||
|
||||
private static Func<Export, bool> MetadataConstraintFor(Type metadataType)
|
||||
{
|
||||
return export => export.Metadata.Any(metadataType.IsInstanceOfType);
|
||||
}
|
||||
|
||||
private ImportCardinality Cardinality
|
||||
{
|
||||
get { return _import.Cardinality; }
|
||||
}
|
||||
|
||||
private Action<Export[], object> BuildSetter()
|
||||
{
|
||||
return _isLazyType
|
||||
? LazySetterFor(_elementType, _setter, Cardinality, MetadataType)
|
||||
: EagerSetterFor(_elementType, _setter, Cardinality);
|
||||
}
|
||||
|
||||
private Type MetadataType
|
||||
{
|
||||
get { return _elementType.GetGenericArguments()[1]; }
|
||||
}
|
||||
|
||||
private static Action<Export[], object> LazySetterFor(Type elementType, Action<object, object> setter, ImportCardinality cardinality, Type metadataType)
|
||||
{
|
||||
if (cardinality == ImportCardinality.Many)
|
||||
return (exports, part) => setter(part, ArrayOf(elementType, exports.Select(e => LazyInstanceFor(elementType, metadataType, e))));
|
||||
return (exports, part) => setter(part, LazyInstanceFor(elementType, metadataType, exports.Single()));
|
||||
}
|
||||
|
||||
private static Action<Export[], object> EagerSetterFor(Type elementType, Action<object, object> setter, ImportCardinality cardinality)
|
||||
{
|
||||
if (cardinality == ImportCardinality.Many)
|
||||
return (exports, part) => setter(part, ArrayOf(elementType, exports.Select(e => e.Value)));
|
||||
return (exports, part) => setter(part, exports.Single().Value);
|
||||
}
|
||||
|
||||
private static Array ArrayOf(Type elementType, IEnumerable<object> elements)
|
||||
{
|
||||
var source = elements.ToArray();
|
||||
var result = Array.CreateInstance(elementType, source.Length);
|
||||
Array.Copy(source, result, source.Length);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static object LazyInstanceFor(Type lazyType, Type metadataType, Export export)
|
||||
{
|
||||
Func<object> factory = () => export.Value;
|
||||
var metadata = export.Metadata.Single(metadataType.IsInstanceOfType);
|
||||
return Activator.CreateInstance(lazyType, new[] { factory, metadata });
|
||||
}
|
||||
|
||||
private Type ContractType()
|
||||
{
|
||||
return _import.ContractType ?? InferContractTypeFromElementType();
|
||||
}
|
||||
|
||||
private Type InferContractTypeFromElementType()
|
||||
{
|
||||
return _isLazyType
|
||||
? _elementType.GetGenericArguments()[0]
|
||||
: _elementType;
|
||||
}
|
||||
|
||||
private Type ElementType()
|
||||
{
|
||||
return _import.Cardinality == ImportCardinality.Many
|
||||
? _actualType.GetElementType()
|
||||
: _actualType;
|
||||
}
|
||||
|
||||
private static bool IsLazyType(Type t)
|
||||
{
|
||||
return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Lazy<,>);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace CodeEditor.Composition.Primitives
|
||||
{
|
||||
static class TypeExtensions
|
||||
{
|
||||
private const BindingFlags InstanceMemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
||||
|
||||
public static MemberInfo[] InstanceMembers(this Type type)
|
||||
{
|
||||
return type.GetMembers(InstanceMemberFlags);
|
||||
}
|
||||
|
||||
public static ConstructorInfo[] InstanceConstructors(this Type type)
|
||||
{
|
||||
return type.GetConstructors(InstanceMemberFlags);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("CodeEditor.Composition")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyProduct("CodeEditor")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
Загрузка…
Ссылка в новой задаче