/**
 * @author XiongJie, Date: 13-9-3
 */
package net.happyonroad.component.core;

import java.util.StringTokenizer;

/**
 * 组件版本，简化自 Maven ArtifactVersion
 */
public class ComponentVersion implements Comparable<ComponentVersion>{
    private Integer majorVersion;

    private Integer minorVersion;

    private Integer incrementalVersion;

    private Integer buildNumber;

    private String qualifier;

    private String unparsed;

    public ComponentVersion(String version)
    {
        parseVersion( version );
    }

    public int compareTo( ComponentVersion otherVersion )
    {

        int result = getMajorVersion() - otherVersion.getMajorVersion();
        if ( result == 0 )
        {
            result = getMinorVersion() - otherVersion.getMinorVersion();
        }
        if ( result == 0 )
        {
            result = getIncrementalVersion() - otherVersion.getIncrementalVersion();
        }
        if ( result == 0 )
        {
            if ( qualifier != null )
            {
                String otherQualifier = otherVersion.getQualifier();

                if ( otherQualifier != null )
                {
                    if ( ( qualifier.length() > otherQualifier.length() )
                         && qualifier.startsWith( otherQualifier ) )
                    {
                        // here, the longer one that otherwise match is considered older
                        result = -1;
                    }
                    else if ( ( qualifier.length() < otherQualifier.length() )
                              && otherQualifier.startsWith( qualifier ) )
                    {
                        // here, the longer one that otherwise match is considered older
                        result = 1;
                    }
                    else
                    {
                        result = qualifier.compareTo( otherQualifier );
                    }
                }
                else
                {
                    // otherVersion has no qualifier but we do - that's newer
                    result = -1;
                }
            }
            else if ( otherVersion.getQualifier() != null )
            {
                // otherVersion has a qualifier but we don't, we're newer
                result = 1;
            }
            else
            {
                result = getBuildNumber() - otherVersion.getBuildNumber();
            }
        }
        return result;
    }

    public int getMajorVersion()
    {
        return majorVersion != null ? majorVersion : 0;
    }

    public int getMinorVersion()
    {
        return minorVersion != null ? minorVersion : 0;
    }

    public int getIncrementalVersion()
    {
        return incrementalVersion != null ? incrementalVersion : 0;
    }

    public int getBuildNumber()
    {
        return buildNumber != null ? buildNumber : 0;
    }

    public String getQualifier()
    {
        return qualifier;
    }

    /**
     * 解析版本字符串
     *   1.0.1-1
     *   1.0.1-snapshot
     *
     * @param version 字符串
     */
    public final void parseVersion( String version )
    {
        unparsed = version;

        int index = version.indexOf( "-" );

        String part1;
        String part2 = null;

        if ( index < 0 )
        {
            part1 = version;
        }
        else
        {
            part1 = version.substring( 0, index );
            part2 = version.substring( index + 1 );
        }

        if ( part2 != null )
        {
            try
            {
                if ( ( part2.length() == 1 ) || !part2.startsWith( "0" ) )
                {
                    buildNumber = Integer.valueOf( part2 );
                }
                else
                {
                    qualifier = part2;
                }
            }
            catch ( NumberFormatException e )
            {
                qualifier = part2;
            }
        }

        if ( !part1.contains(".") && !part1.startsWith( "0" ) )
        {
            try
            {
                majorVersion = Integer.valueOf( part1 );
            }
            catch ( NumberFormatException e )
            {
                // qualifier is the whole version, including "-"
                qualifier = version;
                buildNumber = null;
            }
        }
        else
        {
            boolean fallback = false;

            StringTokenizer tok = new StringTokenizer( part1, "." );
            try
            {
                majorVersion = getNextIntegerToken( tok );
                if ( tok.hasMoreTokens() )
                {
                    minorVersion = getNextIntegerToken( tok );
                }
                if ( tok.hasMoreTokens() )
                {
                    incrementalVersion = getNextIntegerToken( tok );
                }
                if ( tok.hasMoreTokens() )
                {
                    fallback = true;
                }

                // string tokenzier won't detect these and ignores them
                if (part1.contains("..") || part1.startsWith( "." ) || part1.endsWith( "." ) )
                {
                    fallback = true;
                }
            }
            catch ( NumberFormatException e )
            {
                fallback = true;
            }

            if ( fallback )
            {
                // qualifier is the whole version, including "-"
                qualifier = version;
                majorVersion = null;
                minorVersion = null;
                incrementalVersion = null;
                buildNumber = null;
            }
        }
    }

    private static Integer getNextIntegerToken( StringTokenizer tok )
    {
        String s = tok.nextToken();
        if ( ( s.length() > 1 ) && s.startsWith( "0" ) )
        {
            throw new NumberFormatException( "Number part has a leading 0: '" + s + "'" );
        }
        return Integer.valueOf( s );
    }

    public String toString()
    {
        return unparsed;
    }

    public boolean equals( Object other )
    {
        //noinspection SimplifiableIfStatement
        if ( this == other )
        {
            return true;
        }

        return other instanceof ComponentVersion && 0 == compareTo((ComponentVersion) other);

    }

    public int hashCode()
    {
        int result = 1229;

        result = 1223 * result + getMajorVersion();
        result = 1223 * result + getMinorVersion();
        result = 1223 * result + getIncrementalVersion();
        result = 1223 * result + getBuildNumber();

        if ( null != getQualifier() )
        {
            result = 1223 * result + getQualifier().hashCode();
        }

        return result;
    }
}


/**
 * @author XiongJie, Date: 13-9-2
 */
package net.happyonroad.component.core.support;

import net.happyonroad.component.core.ComponentResource;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.Manifest;

/**
 * 组件以目录形式， 未封闭状态下被运行
 */
@SuppressWarnings("UnusedDeclaration")
public class ComponentFolderResource extends ComponentResource {
    protected File folder;

    public ComponentFolderResource(String groupId,
                                   String artifactId,
                                   String version,
                                   File folder) {
        super(groupId, artifactId, version, folder.getName());
        this.folder = folder;
        if( !this.folder.exists() )
            throw new IllegalArgumentException("The component folder: " + folder + " is not exist!");
        try{
            this.manifest = new Manifest(getInputStream("META-INF/MANIFEST.MF"));
        }catch (IOException ioe){
            // NO manifest, create a default
            this.manifest = new Manifest();
        }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // 重载父类方法
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////

    @Override
    public void close() {
        this.manifest.clear();
        this.manifest = null;
    }

    @Override
    public URL getURL() throws MalformedURLException {
        return this.folder.toURI().toURL();
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // 内部实现方法
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////

    @Override
    public InputStream getInputStream(String relativePath) throws IOException {
        File file = new File(folder, relativePath);
        if( !file.exists() )                      {
            String msg = String.format("Can't find any file with relative path = `%s` in folder: `%s`",
                                       relativePath, folder.getAbsolutePath());
            throw new IllegalArgumentException(msg);
        }
        return new FileInputStream(file);
    }

    @Override
    public boolean exists(String relativePath) {
        File file = new File(folder, relativePath);
        return file.exists();
    }

    @Override
    public Resource[] getLocalResourcesUnder(String path) {
        File target = new File(folder, path);
        IOFileFilter all = new IOFileFilter() {
            @Override
            public boolean accept(File file) {
                return true;
            }

            @Override
            public boolean accept(File dir, String name) {
                return true;
            }
        };
        if(target.isDirectory()){
            Collection<File> files = FileUtils.listFiles(target, all, all);
            Set<Resource> resources = new HashSet<Resource>(files.size());
            for (File file : files) {
                resources.add(new FileSystemResource(file));
            }
            return resources.toArray(new Resource[resources.size()]);
        }else{
            return new Resource[]{new FileSystemResource(target)};
        }
    }

    @Override
    public Resource getLocalResourceUnder(String path) {
        File target = new File(folder, path);
        return new FileSystemResource(target);
    }

    @Override
    public URL getLocalResource(String path) {
        try {
            return new File(folder, path).toURI().toURL();
        } catch (MalformedURLException e){
            throw new IllegalArgumentException("Can't create relative url by " + path);
        }
    }
}


/**
 * @author XiongJie, Date: 13-9-3
 */
package net.happyonroad.component.core.support;

import net.happyonroad.component.core.Component;
import net.happyonroad.component.core.ComponentVersion;
import net.happyonroad.component.core.exception.InvalidVersionSpecificationException;
import net.happyonroad.component.core.exception.OverConstrainedVersionException;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

/**
 * 从Maven中copy来的VersionRange
 */
public class VersionRange
{
    private final ComponentVersion recommendedVersion;

    private final List<Restriction> restrictions;

    private VersionRange( ComponentVersion recommendedVersion, List<Restriction> restrictions )
    {
        this.recommendedVersion = recommendedVersion;
        this.restrictions = restrictions;
    }

    public ComponentVersion getRecommendedVersion()
    {
        return recommendedVersion;
    }

    public List<Restriction> getRestrictions()
    {
        return restrictions;
    }

    public VersionRange cloneOf()
    {
        List<Restriction> copiedRestrictions = null;

        if ( restrictions != null )
        {
            copiedRestrictions = new ArrayList<Restriction>();

            if ( !restrictions.isEmpty() )
            {
                copiedRestrictions.addAll( restrictions );
            }
        }

        return new VersionRange( recommendedVersion, copiedRestrictions );
    }

    /**
     * Create a version range from a string representation
     *
     * Some spec examples are
     *

     *
1.0 Version 1.0

     *
[1.0,2.0) Versions 1.0 (included) to 2.0 (not included)

     *
[1.0,2.0] Versions 1.0 to 2.0 (both included)

     *
[1.5,) Versions 1.5 and higher

     *
(,1.0],[1.2,) Versions up to 1.0 (included) and 1.2 or higher

     *

     *
     * @param spec string representation of a version or version range
     * @return a new {@link VersionRange} object that represents the spec
     * @throws net.happyonroad.component.core.exception.InvalidVersionSpecificationException
     */
    public static VersionRange createFromVersionSpec( String spec )
        throws InvalidVersionSpecificationException
    {
        if ( spec == null )
        {
            return null;
        }

        List<Restriction> restrictions = new ArrayList<Restriction>();
        String process = spec;
        ComponentVersion version = null;
        ComponentVersion upperBound = null;
        ComponentVersion lowerBound = null;

        while ( process.startsWith( "[" ) || process.startsWith( "(" ) )
        {
            int index1 = process.indexOf( ")" );
            int index2 = process.indexOf( "]" );

            int index = index2;
            if ( index2 < 0 || index1 < index2 )
            {
                if ( index1 >= 0 )
                {
                    index = index1;
                }
            }

            if ( index < 0 )
            {
                throw new InvalidVersionSpecificationException( "Unbounded range: " + spec );
            }

            Restriction restriction = parseRestriction( process.substring( 0, index + 1 ) );
            if ( lowerBound == null )
            {
                lowerBound = restriction.getLowerBound();
            }
            if ( upperBound != null )
            {
                if ( restriction.getLowerBound() == null || restriction.getLowerBound().compareTo( upperBound ) < 0 )
                {
                    throw new InvalidVersionSpecificationException( "Ranges overlap: " + spec );
                }
            }
            restrictions.add( restriction );
            upperBound = restriction.getUpperBound();

            process = process.substring( index + 1 ).trim();

            if ( process.length() > 0 && process.startsWith( "," ) )
            {
                process = process.substring( 1 ).trim();
            }
        }

        if ( process.length() > 0 )
        {
            if ( restrictions.size() > 0 )
            {
                throw new InvalidVersionSpecificationException(
                    "Only fully-qualified sets allowed in multiple set scenario: " + spec );
            }
            else
            {
                version = new ComponentVersion( process );
                restrictions.add( Restriction.EVERYTHING );
            }
        }

        return new VersionRange( version, restrictions );
    }

    private static Restriction parseRestriction( String spec )
        throws InvalidVersionSpecificationException
    {
        boolean lowerBoundInclusive = spec.startsWith( "[" );
        boolean upperBoundInclusive = spec.endsWith( "]" );

        String process = spec.substring( 1, spec.length() - 1 ).trim();

        Restriction restriction;

        int index = process.indexOf( "," );

        if ( index < 0 )
        {
            if ( !lowerBoundInclusive || !upperBoundInclusive )
            {
                throw new InvalidVersionSpecificationException( "Single version must be surrounded by []: " + spec );
            }

            ComponentVersion version = new ComponentVersion( process );

            restriction = new Restriction( version, true, version, true);
        }
        else
        {
            String lowerBound = process.substring( 0, index ).trim();
            String upperBound = process.substring( index + 1 ).trim();
            if ( lowerBound.equals( upperBound ) )
            {
                throw new InvalidVersionSpecificationException( "Range cannot have identical boundaries: " + spec );
            }

            ComponentVersion lowerVersion = null;
            if ( lowerBound.length() > 0 )
            {
                lowerVersion = new ComponentVersion( lowerBound );
            }
            ComponentVersion upperVersion = null;
            if ( upperBound.length() > 0 )
            {
                upperVersion = new ComponentVersion( upperBound );
            }

            if ( upperVersion != null && lowerVersion != null && upperVersion.compareTo( lowerVersion ) < 0 )
            {
                throw new InvalidVersionSpecificationException( "Range defies version ordering: " + spec );
            }

            restriction = new Restriction( lowerVersion, lowerBoundInclusive, upperVersion, upperBoundInclusive );
        }

        return restriction;
    }

    public static VersionRange createFromVersion( String version )
    {
        //noinspection unchecked
        return new VersionRange( new ComponentVersion( version ), Collections.EMPTY_LIST );
    }

    /**
     * Creates and returns a new VersionRange that is a restriction of this
     * version range and the specified version range.
     *

     * Note: Precedence is given to the recommended version from this version range over the
     * recommended version from the specified version range.
     *

     * @param restriction the VersionRange that will be used to restrict this version
     * range.
     * @return the VersionRange that is a restriction of this version range and the
     * specified version range.
     *

     * The restrictions of the returned version range will be an intersection of the restrictions
     * of this version range and the specified version range if both version ranges have
     * restrictions. Otherwise, the restrictions on the returned range will be empty.
     *

     *

     * The recommended version of the returned version range will be the recommended version of
     * this version range, provided that ranges falls within the intersected restrictions. If
     * the restrictions are empty, this version range's recommended version is used if it is not
     * null. If it is null, the specified version range's recommended
     * version is used (provided it is non-null). If no recommended version can be
     * obtained, the returned version range's recommended version is set to null.
     *

     * @throws NullPointerException if the specified VersionRange is
     * null.
     */
    public VersionRange restrict( VersionRange restriction ) throws OverConstrainedVersionException {
        List<Restriction> r1 = this.restrictions;
        List<Restriction> r2 = restriction.restrictions;
        List<Restriction> restrictions;
        if ( r1.isEmpty() || r2.isEmpty() )
        {
            restrictions = Collections.emptyList();
        }
        else
        {
            restrictions = intersection( r1, r2 );
        }

        ComponentVersion version = null;
        if ( restrictions.size() > 0 )
        {
            boolean found = false;
            for ( Iterator i = restrictions.iterator(); i.hasNext() && !found; )
            {
                Restriction r = (Restriction) i.next();

                if ( recommendedVersion != null && r.containsVersion( recommendedVersion ) )
                {
                    // if we find the original, use that
                    version = recommendedVersion;
                    found = true;
                }
                else if ( version == null && restriction.getRecommendedVersion() != null &&
                    r.containsVersion( restriction.getRecommendedVersion() ) )
                {
                    // use this if we can, but prefer the original if possible
                    version = restriction.getRecommendedVersion();
                }
            }
        }
        // Either the original or the specified version ranges have no restructions
        else if ( recommendedVersion != null )
        {
            // Use the original recommended version since it exists
            version = recommendedVersion;
        }
        else if (restriction.recommendedVersion != null)
        {
            // Use the recommended version from the specified VersionRange since there is no
            // original recommended version
            version = restriction.recommendedVersion;
        }
        else
        {
            //TODO: the component is null
            throw new OverConstrainedVersionException( "Restricting incompatible version ranges", null );
        }

        return new VersionRange( version, restrictions );
    }

    private List<Restriction> intersection( List r1, List r2 )
    {
        List<Restriction> restrictions = new ArrayList<Restriction>( r1.size() + r2.size() );
        Iterator i1 = r1.iterator();
        Iterator i2 = r2.iterator();
        Restriction res1 = (Restriction) i1.next();
        Restriction res2 = (Restriction) i2.next();

        boolean done = false;
        while ( !done )
        {
            if ( res1.getLowerBound() == null || res2.getUpperBound() == null ||
                res1.getLowerBound().compareTo( res2.getUpperBound() ) <= 0 )
            {
                if ( res1.getUpperBound() == null || res2.getLowerBound() == null ||
                    res1.getUpperBound().compareTo( res2.getLowerBound() ) >= 0 )
                {
                    ComponentVersion lower;
                    ComponentVersion upper;
                    boolean lowerInclusive;
                    boolean upperInclusive;

                    // overlaps
                    if ( res1.getLowerBound() == null )
                    {
                        lower = res2.getLowerBound();
                        lowerInclusive = res2.isLowerBoundInclusive();
                    }
                    else if ( res2.getLowerBound() == null )
                    {
                        lower = res1.getLowerBound();
                        lowerInclusive = res1.isLowerBoundInclusive();
                    }
                    else
                    {
                        int comparison = res1.getLowerBound().compareTo( res2.getLowerBound() );
                        if ( comparison < 0 )
                        {
                            lower = res2.getLowerBound();
                            lowerInclusive = res2.isLowerBoundInclusive();
                        }
                        else if ( comparison == 0 )
                        {
                            lower = res1.getLowerBound();
                            lowerInclusive = res1.isLowerBoundInclusive() && res2.isLowerBoundInclusive();
                        }
                        else
                        {
                            lower = res1.getLowerBound();
                            lowerInclusive = res1.isLowerBoundInclusive();
                        }
                    }

                    if ( res1.getUpperBound() == null )
                    {
                        upper = res2.getUpperBound();
                        upperInclusive = res2.isUpperBoundInclusive();
                    }
                    else if ( res2.getUpperBound() == null )
                    {
                        upper = res1.getUpperBound();
                        upperInclusive = res1.isUpperBoundInclusive();
                    }
                    else
                    {
                        int comparison = res1.getUpperBound().compareTo( res2.getUpperBound() );
                        if ( comparison < 0 )
                        {
                            upper = res1.getUpperBound();
                            upperInclusive = res1.isUpperBoundInclusive();
                        }
                        else if ( comparison == 0 )
                        {
                            upper = res1.getUpperBound();
                            upperInclusive = res1.isUpperBoundInclusive() && res2.isUpperBoundInclusive();
                        }
                        else
                        {
                            upper = res2.getUpperBound();
                            upperInclusive = res2.isUpperBoundInclusive();
                        }
                    }

                    // don't add if they are equal and one is not inclusive
                    if ( lower == null || upper == null || lower.compareTo( upper ) != 0 )
                    {
                        restrictions.add( new Restriction( lower, lowerInclusive, upper, upperInclusive ) );
                    }
                    else if ( lowerInclusive && upperInclusive )
                    {
                        restrictions.add( new Restriction( lower, true, upper, true) );
                    }

                    //noinspection ObjectEquality
                    if ( upper == res2.getUpperBound() )
                    {
                        // advance res2
                        if ( i2.hasNext() )
                        {
                            res2 = (Restriction) i2.next();
                        }
                        else
                        {
                            done = true;
                        }
                    }
                    else
                    {
                        // advance res1
                        if ( i1.hasNext() )
                        {
                            res1 = (Restriction) i1.next();
                        }
                        else
                        {
                            done = true;
                        }
                    }
                }
                else
                {
                    // move on to next in r1
                    if ( i1.hasNext() )
                    {
                        res1 = (Restriction) i1.next();
                    }
                    else
                    {
                        done = true;
                    }
                }
            }
            else
            {
                // move on to next in r2
                if ( i2.hasNext() )
                {
                    res2 = (Restriction) i2.next();
                }
                else
                {
                    done = true;
                }
            }
        }

        return restrictions;
    }

    public ComponentVersion getSelectedVersion( Component component)
        throws OverConstrainedVersionException
    {
        ComponentVersion version;
        if ( recommendedVersion != null )
        {
            version = recommendedVersion;
        }
        else
        {
            if ( restrictions.size() == 0 )
            {
                throw new OverConstrainedVersionException( "The Component has no valid ranges", component);
            }

            version = null;
        }
        return version;
    }

    public boolean isSelectedVersionKnown( Component component)
        throws OverConstrainedVersionException
    {
        boolean value = false;
        if ( recommendedVersion != null )
        {
            value = true;
        }
        else
        {
            if ( restrictions.size() == 0 )
            {
                throw new OverConstrainedVersionException( "The Component has no valid ranges", component);
            }
        }
        return value;
    }

    public String toString()
    {
        if ( recommendedVersion != null )
        {
            return recommendedVersion.toString();
        }
        else
        {
            StringBuilder buf = new StringBuilder();
            for ( Iterator i = restrictions.iterator(); i.hasNext(); )
            {
                Restriction r = (Restriction) i.next();

                buf.append( r.toString() );

                if ( i.hasNext() )
                {
                    buf.append( "," );
                }
            }
            return buf.toString();
        }
    }

    public ComponentVersion matchVersion( List versions )
    {
        // TODO: could be more efficient by sorting the list and then moving along the restrictions in order?

        ComponentVersion matched = null;
        for (Object version1 : versions) {
            ComponentVersion version = (ComponentVersion) version1;
            if (containsVersion(version)) {
                // valid - check if it is greater than the currently matched version
                if (matched == null || version.compareTo(matched) > 0) {
                    matched = version;
                }
            }
        }
        return matched;
    }

    public boolean containsVersion( ComponentVersion version )
    {
        for (Object restriction1 : restrictions) {
            Restriction restriction = (Restriction) restriction1;
            if (restriction.containsVersion(version)) {
                return true;
            }
        }
        return false;
    }

    public boolean hasRestrictions()
    {
        return !restrictions.isEmpty() && recommendedVersion == null;
    }

    public boolean equals( Object obj )
    {
        if (this == obj){
            return true;
        }
        if (!(obj instanceof VersionRange ))
        {
            return false;
        }
        VersionRange other = (VersionRange) obj;

        boolean equals =
            recommendedVersion == other.recommendedVersion ||
                ( ( recommendedVersion != null ) && recommendedVersion.equals( other.recommendedVersion ) );
        equals &=
            restrictions == other.restrictions ||
                ( ( restrictions != null ) && restrictions.equals( other.restrictions ) );
        return equals;
    }

    public int hashCode()
    {
        int hash = 7;
        hash = 31 * hash + ( recommendedVersion == null ? 0 : recommendedVersion.hashCode() );
        hash = 31 * hash + ( restrictions == null ? 0 : restrictions.hashCode() );
        return hash;
    }
}


/**
 * @author XiongJie, Date: 13-9-2
 */
package net.happyonroad.component.core;

import org.apache.commons.io.IOUtils;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.jar.Manifest;

/**
 * 组件的资源
 */
public abstract class ComponentResource extends DefaultResourceLoader {
    protected final String groupId, artifactId;
    private final String   version;
    private final String   fileName;
    protected     Manifest manifest;
    //META-INF目录下INDEX.DETAIL文件的内容
    // 如果没有该文件，或者该文件内容为空，则该对象为空数组
    // 如果还没有初始化过indexes，则该对象为null
    protected     String[] indexes;
    protected     String[] dependencies;

    protected ComponentResource(String groupId, String artifactId, String version, String fileName) {
        this.groupId = groupId;
        this.artifactId = artifactId;
        this.version = version;
        this.fileName = fileName;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // 主要对外公开API
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public String getGroupId(){
        return groupId;
    }

    public String getArtifactId(){
        return artifactId;
    }

    public String getVersion() {
        return version;
    }

    public String getFileName() {
        return fileName;
    }

    public InputStream getPomStream() throws IOException {
        return getInputStream(getPomXmlPath());
    }

    protected String getPomXmlPath() {
        return "META-INF/maven/" + getGroupId() + "/" + getArtifactId() + "/pom.xml";
    }

    public InputStream getApplicationStream() throws IOException {
        return getInputStream("META-INF/application.xml");
    }

    public InputStream getServiceStream() throws IOException {
        return getInputStream("META-INF/service.xml");
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // 次要对外API
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////
    public Manifest getManifest() {
        return manifest;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // 内部实现方法
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////
    public abstract InputStream getInputStream(String relativePath) throws IOException;

    public abstract boolean exists(String relativePath);

    public boolean isPomXmlExists(){
        return exists(getPomXmlPath());
    }

    public abstract void close();

    //获取某个目录下所有的资源
    // 如果传入的是文件名称，则返回该文件对应的资源
    public abstract Resource[] getLocalResourcesUnder(String path);

    public abstract Resource getLocalResourceUnder(String path);

    public abstract URL getURL() throws MalformedURLException;

    /**
     * 判断本组件包是否被索引
     *
     * @return 存在META-INF/INDEX.DETAIL 文件即为被索引
     */
    public boolean isIndexed() {
        if( indexes == null ){
            InputStream stream = null;
            try {
                stream = getInputStream("META-INF/INDEX.DETAIL");
                List<String> lines = IOUtils.readLines(stream);
                //是否应该讲indexes hash化？
                indexes = lines.toArray(new String[lines.size()]);
                // to be quick searched
                Arrays.sort(indexes);
            } catch (Exception e) {
                indexes = new String[0];
            } finally {
                IOUtils.closeQuietly(stream);
            }
        }
        return indexes.length > 0 ;
    }

    /**
     * 根据index信息，判断本组件包是否包含特点文件
     * @param path 资源的路径
     * @return 是否存在
     */
    public boolean contains(String path) {
        if( isIndexed() ){
            return Arrays.binarySearch(indexes, path) >= 0;
        }else{
            return getLocalResourceUnder(path) != null;
        }
    }

    /**
     * 获取资源url
     * @param path 资源在组件包里面的路径
     * @return 路径
     */
    public abstract URL getLocalResource(String path);

    public String[] getDependencies(){
        if( dependencies == null ){
            InputStream stream = null;
            try {
                stream = getInputStream("META-INF/INDEX.DEPENDS");
                List<String> lines = IOUtils.readLines(stream);
                //是否应该讲indexes hash化？
                dependencies = lines.toArray(new String[lines.size()]);
            } catch (Exception e) {
                dependencies = new String[0];
            } finally {
                IOUtils.closeQuietly(stream);
            }
        }
        return dependencies;
    }

}


/**
 * @author XiongJie, Date: 13-9-3
 */
package net.happyonroad.component.container.support;

import net.happyonroad.component.container.AppLauncher;
import net.happyonroad.component.container.ComponentLoader;
import net.happyonroad.component.container.Executable;
import net.happyonroad.component.container.LaunchEnvironment;
import net.happyonroad.component.container.event.ContainerEvent;
import net.happyonroad.component.container.event.ContainerStartedEvent;
import net.happyonroad.component.container.event.ContainerStoppingEvent;
import net.happyonroad.component.core.Component;
import net.happyonroad.component.core.ComponentContext;
import net.happyonroad.component.core.FeatureResolver;
import net.happyonroad.component.core.exception.DependencyNotMeetException;
import net.happyonroad.component.core.exception.InvalidComponentNameException;
import net.happyonroad.component.core.support.DefaultComponent;
import net.happyonroad.spring.context.ContextUtils;
import net.happyonroad.util.LogUtils;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ApplicationEvent;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.*;

import static org.apache.commons.lang.time.DurationFormatUtils.formatDurationHMS;

/**
 * 为了让Launch静态程序可以被定制化，从原 Plexus/Launch中分离出来的对象
 */
public class DefaultLaunchEnvironment implements LaunchEnvironment {
    public static final int MODE_START = 1;
    public static final int MODE_STOP  = -1;

    private Logger logger = LoggerFactory.getLogger(AppLauncher.class.getName());

    private DefaultComponentRepository repository;
    private ComponentLoader            loader;
    private ComponentContext           context;

    public DefaultLaunchEnvironment() {
        String home = System.getProperty("app.home");
        if (!StringUtils.hasText(home)) {
            home = System.getProperty("user.dir");
            System.setProperty("app.home", home);
            logger.debug("app.home is not set, use user.dir as app.home: {}", home);
        }
        home = FilenameUtils.normalize(System.getProperty("app.home"));
        //noinspection ConstantConditions
        System.setProperty("app.home", home);

        String appHost = System.getProperty("app.host");
        if (!StringUtils.hasText(appHost)) {
            List<String> addresses = getLocalAddresses();
            if (addresses.isEmpty()) {
                throw new ApplicationContextException("You server isn't configure any address");
            }
            System.setProperty("app.host", addresses.get(0));
            logger.debug("app.host is not set, use local ip as default");
        }
        repository = createComponentRepository(home);
        try {
            repository.start();
        } catch (Exception e) {
            throw new UnsupportedOperationException("Can't start the default component repository, " +
                                                    "because of: " + e.getMessage(), e);
        }
    }

    /**
     * 根据Main Resource的元信息，创建相应的Launcher对象
     *
     * @param component 主启动组件
     * @return App Launcher对象
     */
    @Override
    public AppLauncher createLauncher(Component component) throws IOException {
        AppLauncher launcher = new AppLauncher(component, this);
        //配置 class world
        this.loader = createLoader(component);
        this.context = (ComponentContext) this.loader;
        return launcher;
    }

    @Override
    public void execute(AppLauncher launcher, String[] args) throws Exception {
        List<String> list = new ArrayList<String>(args.length);
        Collections.addAll(list, args);
        String host = null;
        int index = 0;
        int mode, port = 0;
        if (list.contains("--host")) {
            host = list.get(list.indexOf("--host") + 1);
        }
        if (list.contains("--index")) {
            index = Integer.valueOf(list.get(list.indexOf("--index") + 1));
        }
        if (list.contains("--port")) {
            port = Integer.parseInt(list.get(list.indexOf("--port") + 1));
        }
        if (list.contains("--stop")) {
            mode = MODE_STOP;
            if (host == null) host = "localhost";
        } else {
            mode = MODE_START;
            host = null;
        }
        Executable executable = configure(launcher, host, index, port);
        if (mode == MODE_START) {
            executable.start();
        } else {
            executable.exit();
        }
    }

    protected Executable configure(AppLauncher launcher, String host, int index, int port) {
        //设定了通过本地端口与已经启动的进程通讯
        if (host != null) {
            if (port > 0) {
                return new LauncherThroughPort(port);
            }else{
                return new LauncherThroughFile(index);
            }
        }// itself
        return launcher;
    }

    // ------------------------------------------------------------
    //     对 ComponentRepository 代理接口的实现
    // ------------------------------------------------------------

    @Override
    public Component resolveComponent(String strDependency)
            throws DependencyNotMeetException, InvalidComponentNameException {
        return repository.resolveComponent(strDependency);
    }

    /**
     * 自顶向下的加载组件
     *
     * @param component 被加载的组件
     */
    public void load(Component component) throws Exception {
        long start = System.currentTimeMillis();
/*
        Set<Component> dependedComponents = component.getAllDependedComponents();
        ArrayList<Component> depends = new ArrayList<Component>(dependedComponents);
        repository.sortComponents(depends);
        if( logger.isDebugEnabled() ){
            logger.debug("Loading components by: \n\t {}",
                        org.apache.commons.lang.StringUtils.join(depends, "\n\t"));
        }
        //把自己放在最后面
        depends.add(component);
        //已经按照依赖排序了
        for (Component depend : depends) {
            loader.quickLoad(depend);
        }
*/
        loader.load(component);
        registerWorldAndComponents(component);

        banner("Container starts took {}", formatDurationHMS(System.currentTimeMillis() - start));

        publishContainerStartedEvent();
    }

    void registerWorldAndComponents(Component component) {
        ApplicationContext mainContext = component.getApplication();
        if (mainContext == null) return;
        MBeanExporter exporter;
        try {
            exporter = mainContext.getBean(MBeanExporter.class);
        } catch (BeansException e) {
            logger.warn("You should configure a MBeanExporter in main app context");
            return;
        }
        List<Component> components = repository.getComponents();
        for (Component comp : components) {
            if (repository.isApplication(comp.getGroupId())) {
                exporter.registerManagedResource(comp, ((DefaultComponent) comp).getObjectName());
            }
        }
    }

    public void unload(Component component) {
        publishContainerStoppingEvent();
        long start = System.currentTimeMillis();
        loader.unload(component);
        banner("Container stops took {}", formatDurationHMS(System.currentTimeMillis() - start));
    }

    public List<ApplicationContext> getApplications() {
        return context.getApplicationFeatures();
    }

    void publishContainerStartedEvent() {
        logger.debug("The component container is started");
        ContainerStartedEvent event = new ContainerStartedEvent(this);
        publishEvent(event);
    }

    void publishContainerStoppingEvent() {
        logger.debug("The component container is stopping");
        ContainerEvent event = new ContainerStoppingEvent(this);
        publishEvent(event);
    }

    void publishEvent(ApplicationEvent event) {
        List<ApplicationContext> applications = getApplications();
        ContextUtils.publishEvent(applications, event);
    }

    @Override
    public void shutdown() {
        banner("Shutting down...");
        if (repository != null) {
            repository.stop();
        }
    }

    protected DefaultComponentRepository createComponentRepository(String home) {
        return new DefaultComponentRepository(home);
    }

    protected ComponentLoader createLoader(Component component) {
        DefaultComponentLoader loader = new DefaultComponentLoader(repository);
        String featureResolvers = System.getProperty("component.feature.resolvers");
        if (StringUtils.hasText(featureResolvers)) {
            for (String resolverFqn : featureResolvers.split(",")) {
                try {
                    logger.debug("Found extended feature resolver: " + resolverFqn);
                    Class resolverClass = Class.forName(resolverFqn, true, component.getClassLoader());
                    FeatureResolver resolver = (FeatureResolver) resolverClass.newInstance();
                    loader.registerResolver(resolver);
                } catch (Exception ex) {
                    logger.error("Can't instantiate the feature resolver: " + resolverFqn, ex);
                }
            }
        }
        return loader;
    }

    void banner(String message, Object... args) {
        logger.info(LogUtils.banner(message, args));
    }

    public static List<String> getLocalAddresses() {
        List<IndexAndIp> localAddresses = new ArrayList<IndexAndIp>(2);
        try {
            Enumeration<NetworkInterface> nics = NetworkInterface.getNetworkInterfaces();
            while (nics.hasMoreElements()) {
                NetworkInterface nic = nics.nextElement();
                Enumeration<InetAddress> addresses = nic.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    InetAddress address = addresses.nextElement();
                    String hostAddress = address.getHostAddress();
                    if (hostAddress.contains(":")) continue; // ipv6
                    if ("127.0.0.1".equals(hostAddress)) continue;
                    localAddresses.add(new IndexAndIp(nic.getIndex(), hostAddress));
                }
            }
        } catch (Exception e) {
            throw new UnsupportedOperationException("Can't find local address", e);
        }
        //按照网卡接口号先后排序
        Collections.sort(localAddresses);
        List<String> addresses = new ArrayList<String>();
        for (IndexAndIp indexAndIp : localAddresses) {
            addresses.add(indexAndIp.ip);
        }
        return addresses;
    }

    static class IndexAndIp implements Comparable<IndexAndIp> {
        private int    index;
        private String ip;

        public IndexAndIp(int index, String ip) {
            this.index = index;
            this.ip = ip;
        }

        @Override

        public int compareTo(@SuppressWarnings("NullableProblems") IndexAndIp another) {
            return this.index - another.index;
        }
    }
}


/**
 * @author XiongJie, Date: 13-9-13
 */
package net.happyonroad.component.container.support;

import net.happyonroad.component.classworld.MainClassLoader;
import net.happyonroad.component.container.ComponentResolver;
import net.happyonroad.component.container.MutableComponentRepository;
import net.happyonroad.component.core.Component;
import net.happyonroad.component.core.ComponentResource;
import net.happyonroad.component.core.exception.DependencyNotMeetException;
import net.happyonroad.component.core.exception.InvalidComponentNameException;
import net.happyonroad.component.core.support.ComponentJarResource;
import net.happyonroad.component.core.support.ComponentUtils;
import net.happyonroad.component.core.support.DefaultComponent;
import net.happyonroad.component.core.support.Dependency;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import static net.happyonroad.util.LogUtils.banner;

/**
 * 组件仓库
 * 请注意，该对象的工作依赖系统环境变量 app.home
 */
public class DefaultComponentRepository implements MutableComponentRepository {

    private static DefaultComponentRepository sharedInstance;

    private Logger logger = LoggerFactory.getLogger(DefaultComponentRepository.class);

    private File                      home;
    private String                    mainComponentId;
    private List<Component>           components;
    private Map<Dependency, Resource> cache;

    /*package*/ ComponentResolver resolver;

    /**
     * 构建一个缺省组件仓库
     *
     * @param home 应用根路径
     */
    public DefaultComponentRepository(String home) {
        sharedInstance = this;
        Thread.currentThread().setContextClassLoader(MainClassLoader.getInstance());
        this.home = new File(home);
        ensureFolders(home, "app", "lib");
        components = new LinkedList<Component>();
        cache = new HashMap<Dependency, Resource>();
        resolver = new DefaultComponentResolver(this);
    }

    protected void ensureFolders(String home, String... folders) {
        for (String folder : folders) {
            File file = new File(home, folder);
            if (!file.exists()) {
                try {
                    FileUtils.forceMkdir(file);
                } catch (IOException e) {
                    throw new IllegalArgumentException("The folder [" + file.getPath()
                                                       + "] not exist and can't be auto created: " + e.getMessage());
                }
            }
        }
    }

    // ------------------------------------------------------------
    //     本身的Lifecycle方法
    // ------------------------------------------------------------

    /** 启动时预加载 lib目录, lib/poms 下所有的组件信息 */
    public void start() {
        logger.info(banner("Scanning jars"));
        //先寻找 boot/*.jar，将其预加载为component
        File bootFolder = new File(home, "boot");
        try {
            scanJars(bootFolder);
        } catch (Exception e) {
            logger.error("Failed to scan {} dir: {}", bootFolder, e.getMessage());
        }

        File appFolder = new File(home, "app");
        try {
            scanJars(appFolder);
        } catch (Exception e) {
            logger.error("Failed to scan {} dir for jars: {}", appFolder, e.getMessage());
        }
        //再寻找 lib/*.jar，将其预加载为component
        File libFolder = new File(home, "lib");
        try {
            scanJars(libFolder);
        } catch (Exception e) {
            logger.error("Failed to scan {} dir for jars: {}", libFolder, e.getMessage());
        }
        File poms = libFolder;
        try {
            //而后再遍历lib/poms下面的pom.xml，把packaging = pom 的父pom预加载进来
            scanPoms(poms);
        } catch (Exception e) {
            logger.error("Failed to scan {} dir for poms: {}", poms, e.getMessage());
        }
        File pomsJar = new File(libFolder, "poms.jar");
        if( pomsJar.exists())
            try {
                //而后再遍历lib/poms下面的pom.xml，把packaging = pom 的父pom预加载进来
                scanPomsJar(pomsJar);
            } catch (Exception e) {
                logger.error("Failed to scan {} poms.jar in {}", poms, e.getMessage());
            }

        //最后还要扫描扩展仓库目录 repository/*.jar
        File repositoryFolder = new File(home, "repository");
        try {
            scanJars(repositoryFolder);
        } catch (Exception e) {
            logger.error("Failed to scan {} dir for jars: {}", repositoryFolder, e.getMessage());
        }
        poms = repositoryFolder;
        try {
            //而后再遍历repository/poms下面的pom.xml，把不存在对应jar的 group pom预加载进来
            scanPoms(poms);
        } catch (Exception e) {
            logger.error("Failed to scan {} dir for poms: {}", poms, e.getMessage());
        }
        pomsJar = new File(repositoryFolder, "poms.jar");
        if( pomsJar.exists())
            try {
                //而后再遍历lib/poms下面的pom.xml，把packaging = pom 的父pom预加载进来
                scanPomsJar(pomsJar);
            } catch (Exception e) {
                logger.error("Failed to scan poms.jar in {}: {}", poms, e.getMessage());
            }
        logger.info(banner("Scanned  jars"));
    }

    private void scanJars(File folder) throws InvalidComponentNameException, IOException {
        if( !folder.isDirectory() ) return;
        Collection<File> jars = FileUtils.listFiles(folder, new String[]{"jar"}, true);
        logger.debug("Scanning {}", folder.getAbsolutePath());
        for (File jar : jars) {
            if(jar.getPath().endsWith("-sources.jar")){
                logger.trace("Skip sources {}", jar.getPath());
                continue;
            }
            if( jar.getName().equalsIgnoreCase("wrapper.jar")){
                logger.trace("Skip wrapper.jar");
                continue;
            }
            if( jar.getName().equalsIgnoreCase("poms.jar")){
                logger.trace("Skip poms.jar");
                continue;
            }
            Dependency dependency;
            String briefId;
            if( jar.getName().contains("spring-component-framework")){
                String version = StringUtils.substringBetween(jar.getName(), "@", ".jar");
                dependency = new Dependency("net.happyonroad", "spring-component-framework", version);
                briefId = "net.happyonroad/" + FilenameUtils.getName(jar.getName());
            }else{
                dependency = Dependency.parse(jar);
                briefId = ComponentUtils.relativePath(jar);
            }
            //先放到cache里面，避免构建 component jar resource时无法寻址
            cache.put(dependency, new FileSystemResource(jar));
            ComponentJarResource resource = null;
            InputStream stream = null;
            try {
                resource = new ComponentJarResource(dependency, briefId);
                stream = resource.getPomStream();
                //只有存在 pom.xml 的jar包才直接解析
                //而不存在 pom.xml 的jar包，需要依赖 poms里面的定义进行解析
            } catch (IOException e) {
                cache.remove(dependency);
            } catch (IllegalArgumentException e) {
                //Not cache it by the concrete jar
                cache.remove(dependency);
            } catch (Exception e) {
                cache.remove(dependency);
                throw new IOException("Failed scan " + jar.getPath(), e);
            } finally {
                try {
                    if (stream != null) stream.close();
                    if(resource != null ) resource.close();
                } catch (IOException e) {
                    logger.error("Error while close:" + jar.getPath(), e);
                }
            }
            logger.trace("Mapping {} -> {}", dependency, jar);
        }
        logger.debug("Scanned  {}", folder.getAbsolutePath());

    }

    private void scanPoms(File poms) throws InvalidComponentNameException {
        if( !poms.isDirectory() ) return;
        logger.trace("Scan {}", poms.getAbsolutePath());
        Collection<File> pomFiles = FileUtils.listFiles(poms, new String[]{"pom"}, true);
        for (File pomFile : pomFiles) {
            Dependency dependency = Dependency.parse(pomFile);
            //如果lib/*.jar已经设置了相应的依赖解析路径，那么不需要在这里再重新设置
            //注意，Dependency的equals和hashCode方法被重写过，所以，指向 jar和pom的依赖是一回事
            if (cache.get(dependency) != null) continue;
            cache.put(dependency, new FileSystemResource(pomFile));
            logger.trace("Mapping {} -> {}", dependency, pomFile);
        }
    }

    private void scanPomsJar(File pomsJar) throws InvalidComponentNameException {
        logger.trace("Scan {}", pomsJar.getAbsolutePath());
        try {
            JarFile jarFile = new JarFile(pomsJar);
            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                if(entry.getName().endsWith(".pom")){
                    Dependency dependency = Dependency.parse(entry.getName());
                    if (cache.get(dependency) != null) continue;
                    byte[] bytes = IOUtils.toByteArray(jarFile.getInputStream(entry));
                    cache.put(dependency, new ByteArrayResource(bytes, entry.getName()));
                    logger.trace("Mapping {} -> {}", dependency, jarFile.getName() + "!/" + entry.getName());
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("Can't scan poms from " + pomsJar.getAbsolutePath());
        }

    }

    public void stop() {
        logger.info(banner("{} Stopping", getClass().getSimpleName()));
        cache.clear();
        //卸载application/service是开发者的责任，应该通过调用 LaunchEnvironment#unload(mainComponent)方法实现
        for (Component component : components) {
            ComponentResource resource = component.getResource();
            if (resource != null) resource.close();
        }
        components.clear();
        logger.info(banner("{} Stopped", getClass().getSimpleName()));
    }

    // ------------------------------------------------------------
    //     对外业务接口方法
    // ------------------------------------------------------------

    @Override
    public Component resolveComponent(Dependency dependency)
            throws InvalidComponentNameException, DependencyNotMeetException {
        List<Component> meets = resolveComponents(dependency);
        if (meets.isEmpty()) {
            Set<Dependency> dependencies = new HashSet<Dependency>(cache.keySet());
            for (Dependency dep : dependencies) {
                if (dependency.accept(dep)) {
                    Resource resource = cache.get(dep);
                    meets.add(resolver.resolveComponent(dependency, resource));
                    //cache.remove(dep);
                }
            }
        }
        if (meets.isEmpty()) {
            logger.trace("Can't find {} in current repository", dependency);
            throw new DependencyNotMeetException(dependency);
        }
        return meets.get(0);
    }

    @Override
    public Component resolveComponent(String strDependency)
            throws DependencyNotMeetException, InvalidComponentNameException {
        if( this.mainComponentId == null ) mainComponentId = strDependency;
        Dependency dependency = Dependency.parse(strDependency);
        return resolveComponent(dependency);
    }

    /**
     * 从当前已经解析的组件中找到符合依赖的组件列表，这不会触发实际解析动作
     *
     * @param dependency 依赖信息
     * @return 组件列表
     */
    @Override
    public List<Component> resolveComponents(Dependency dependency) {
        logger.trace("Finding   {}", dependency);
        List<Component> meets = new ArrayList<Component>();
        for (Component component : components) {
            if (dependency.accept(component)) {
                meets.add(component);
            }
        }
        if (meets.isEmpty()) {
            logger.trace("Missing   {}", dependency);
        } else {
            logger.trace("Found     {} {} components", dependency, meets.size());
        }
        //对所有满足的依赖进行排序
        Collections.sort(meets);
        //排序的原则是越高版本越好
        Collections.reverse(meets);
        return meets;
    }

    @Override
    public List<Component> getComponents() {
        return Collections.unmodifiableList(components);
    }

    @Override
    public boolean cached(File file) {
        for(Resource resource : cache.values() ){
            try {
                if(resource.getFile().equals(file)){
                    return true;
                }
            } catch (IOException e) {
                //skip, continue for next
            }
        }
        return false;
    }

    @Override
    public void cache(File file) throws InvalidComponentNameException {
        Dependency dep = Dependency.parse(file);
        cache.put(dep, new FileSystemResource(file));
    }

    @Override
    public void sortCandidates(File[] candidateComponentJars)
            throws DependencyNotMeetException, InvalidComponentNameException {
        final List<Component> components = new ArrayList<Component>(candidateComponentJars.length);
        final Map<File, Component> mapping = new HashMap<File, Component>();
        for (File file : candidateComponentJars) {
            Component component;
            try {
                component = resolveComponent(file.getPath());
            } catch (DependencyNotMeetException e) {
                continue;//  some file, such as spring-test, maybe excluded
            }
            components.add(component);
            mapping.put(file, component);
        }
        sortComponents(components);
        Arrays.sort(candidateComponentJars, new Comparator<File>() {
            @Override
            public int compare(File f1, File f2) {
                return positionOf(f1) - positionOf(f2);
            }

            private int positionOf(File file) {
                Component component = mapping.get(file);
                if( component == null ) return 1000;
                return components.indexOf(component);
            }
        });
    }

    /**
     * 基于依赖关系进行排序，最多被依赖的组件排在最前面，没有被依赖的排在最后面
     * <pre>
     * 这个排序算法不能基于java的quick sort，默认快速排序，排到前面的，不会再和后面的继续比较
     *
     * 基本的算法原理是：
     * 先为components集合构建一个相互之间的依赖关系，map结构
     *
     * </pre>
     * @param components 被排序的组件
     */
    @Override
    public void sortComponents(List<Component> components) {
        //将组件集合之间的依赖关系以map的形式展现出来
        Map<Component, Set<Component>> dependencies = dependsMap(components);
        //清空待排序的components，用于做结果的容器
        components.clear();
        //基于依赖关系进行处理
        arrange(components, dependencies);
    }

    Map<Component, Set<Component>> dependsMap(List<Component> components) {
        Map<Component, Set<Component>> dependencies = new HashMap<Component, Set<Component>>();
        for (Component component : components) {
            Set<Component> depends = new HashSet<Component>();
            for (Component other : components) {
                if( component == other ) continue;
                if( component.dependsOn(other) )
                    depends.add(other);
            }
            dependencies.put(component, depends);
        }
        return dependencies;
    }

    void arrange(List<Component> result, Map<Component, Set<Component>> dependencies) {
        Iterator<Map.Entry<Component, Set<Component>>> it = dependencies.entrySet().iterator();
        boolean cycled = true;
        while (it.hasNext()) {
            Map.Entry<Component, Set<Component>> entry = it.next();
            if( entry.getValue().isEmpty() ){
                cycled = false;
                //从依赖数组中移除当前这个没有依赖任何其他组件的对象关系
                it.remove();
                //并将对象放到结果集合头部
                result.add(entry.getKey());
                //而后将所有其他的依赖关系中指向该记录的清除掉
                for (Set<Component> list : dependencies.values()) {
                    list.remove(entry.getKey());
                }
            }
        }
        if(cycled && !dependencies.isEmpty())
            throw new IllegalArgumentException("There are cycle dependencies in: "
                                               + Arrays.toString(dependencies.keySet().toArray()));
        if( !dependencies.isEmpty() ) arrange(result, dependencies);
    }

    // ------------------------------------------------------------
    //     提供给内部实现者
    // ------------------------------------------------------------
    @Override
    public void addComponent(Component component) {
        logger.debug("Register  {} into repository", component);
        components.add(component);
    }

    @Override
    public void removeComponent(Component component) {
        logger.trace("Removing {} from repository", component);
        components.remove(component);
    }

    @Override
    public boolean isApplication(String groupId) {
        return DefaultComponent.isApplication(groupId);
    }

    @Override
    public String getHome() {
        return home.getPath();
    }

    public static DefaultComponentRepository getRepository() {
        return sharedInstance;
    }
}


/**
 * BoxTransform.java
 *
 * Created on 22. 11. 2016, 18:43:16 by burgetr
 */
package org.fit.layout.cssbox;

import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;

import org.fit.cssbox.layout.Box;
import org.fit.cssbox.layout.CSSDecoder;
import org.fit.cssbox.layout.ElementBox;
import org.fit.layout.model.Rectangular;

import cz.vutbr.web.css.CSSProperty;
import cz.vutbr.web.css.Term;
import cz.vutbr.web.css.TermAngle;
import cz.vutbr.web.css.TermFunction;
import cz.vutbr.web.css.TermInteger;
import cz.vutbr.web.css.TermLengthOrPercent;
import cz.vutbr.web.css.TermList;
import cz.vutbr.web.css.TermNumber;

/**
 * A transformation applied to a box according to the "transform" CSS property.
 *
 * @author burgetr
 */
public class BoxTransform
{
    private Box box;
    private AffineTransform transform;

    /**
     * Creates a transformation for the given box based on its CSS properties. An empty transformation
     * is created for boxes that are not element boxes (they have no CSS style assigned).
     * @param box the source box
     */
    public BoxTransform(Box box)
    {
        this.box = box;
        if (box instanceof ElementBox)
            transform = createTransform((ElementBox) box);
        else
            transform = null;
    }

    public BoxTransform(BoxTransform src)
    {
        box = src.box;
        transform = src.transform;
    }

    /**
     * Obtains the source box.
     * @return The box that this transformation belongs to.
     */
    public Box getBox()
    {
        return box;
    }

    /**
     * Obtains the transformation that should be applied.
     * @return The transformation object or {@code null} if no transformation should be applied.
     */
    public AffineTransform getTransform()
    {
        return transform;
    }

    /**
     * Checks if the transformation is empty (no transformation is applied).
     * @return {@code true} when no transformation is applied.
     */
    public boolean isEmpty()
    {
        return (transform == null);
    }

    /**
     * Concatenates another transformation to this transformation.
     * @param src
     * @return A new concatenated transformation.
     */
    public BoxTransform concatenate(BoxTransform src)
    {
        if (src.isEmpty())
            return this;
        else if (this.isEmpty())
            return src;
        else
        {
            BoxTransform ret = new BoxTransform(this);
            ret.transform = new AffineTransform(transform);
            ret.transform.concatenate(src.transform);
            return ret;
        }
    }

    /**
     * Transforms a rectangle to other rectangle using the given transformation.
     * @param rect the source rectangle
     * @return the bounding box of the transformed rectangle.
     */
    public Rectangular transformRect(Rectangular rect)
    {
        if (transform != null)
        {
            Rectangle src = new Rectangle(rect.getX1(), rect.getY1(), rect.getWidth(), rect.getHeight());
            Shape dest = transform.createTransformedShape(src);
            Rectangle destr;
            if (dest instanceof Rectangle)
                destr = (Rectangle) dest;
            else
                destr = dest.getBounds();
            return new Rectangular(destr);
        }
        else
            return rect;
    }

    //=========================================================================================================================

    protected AffineTransform createTransform(ElementBox elem)
    {
        if (elem.isBlock() || elem.isReplaced())
        {
            CSSDecoder dec = new CSSDecoder(elem.getVisualContext());
            Rectangle bounds = elem.getAbsoluteBorderBounds();
            //decode the origin
            int ox, oy;
            CSSProperty.TransformOrigin origin = elem.getStyle().getProperty("transform-origin");
            if (origin == CSSProperty.TransformOrigin.list_values)
            {
                TermList values = elem.getStyle().getValue(TermList.class, "transform-origin");
                ox = dec.getLength((TermLengthOrPercent) values.get(0), false, bounds.width / 2, 0, bounds.width);
                oy = dec.getLength((TermLengthOrPercent) values.get(1), false, bounds.height / 2, 0, bounds.height);
            }
            else
            {
                ox = bounds.width / 2;
                oy = bounds.height / 2;
            }
            ox += bounds.x;
            oy += bounds.y;
            //compute the transformation matrix
            AffineTransform ret = null;
            CSSProperty.Transform trans = elem.getStyle().getProperty("transform");
            if (trans == CSSProperty.Transform.list_values)
            {
                ret = new AffineTransform();
                ret.translate(ox, oy);
                TermList values = elem.getStyle().getValue(TermList.class, "transform");
                for (Term<?> term : values)
                {
                    if (term instanceof TermFunction)
                    {
                        final TermFunction func = (TermFunction) term;
                        final String fname = func.getFunctionName().toLowerCase();
                        if (fname.equals("rotate"))
                        {
                            if (func.size() == 1 && func.get(0) instanceof TermAngle)
                            {
                                double theta = dec.getAngle((TermAngle) func.get(0));
                                ret.rotate(theta);
                            }
                        }
                        else if (fname.equals("translate"))
                        {
                            if (func.size() == 1 && func.get(0) instanceof TermLengthOrPercent)
                            {
                                int tx = dec.getLength((TermLengthOrPercent) func.get(0), false, 0, 0, bounds.width);
                                ret.translate(tx, 0.0);
                            }
                            else if (func.size() == 2 && func.get(0) instanceof TermLengthOrPercent && func.get(1) instanceof TermLengthOrPercent)
                            {
                                int tx = dec.getLength((TermLengthOrPercent) func.get(0), false, 0, 0, bounds.width);
                                int ty = dec.getLength((TermLengthOrPercent) func.get(1), false, 0, 0, bounds.height);
                                ret.translate(tx, ty);
                            }
                        }
                        else if (fname.equals("translatex"))
                        {
                            if (func.size() == 1 && func.get(0) instanceof TermLengthOrPercent)
                            {
                                int tx = dec.getLength((TermLengthOrPercent) func.get(0), false, 0, 0, bounds.width);
                                ret.translate(tx, 0.0);
                            }
                        }
                        else if (fname.equals("translatey"))
                        {
                            if (func.size() == 1 && func.get(0) instanceof TermLengthOrPercent)
                            {
                                int ty = dec.getLength((TermLengthOrPercent) func.get(0), false, 0, 0, bounds.height);
                                ret.translate(0.0, ty);
                            }
                        }
                        else if (fname.equals("scale"))
                        {
                            if (func.size() == 1 && isNumber(func.get(0)))
                            {
                                float sx = getNumber(func.get(0));
                                ret.scale(sx, sx);
                            }
                            else if (func.size() == 2 && isNumber(func.get(0)) && isNumber(func.get(1)))
                            {
                                float sx = getNumber(func.get(0));
                                float sy = getNumber(func.get(1));
                                ret.scale(sx, sy);
                            }
                        }
                        else if (fname.equals("scalex"))
                        {
                            if (func.size() == 1 && isNumber(func.get(0)))
                            {
                                float sx = getNumber(func.get(0));
                                ret.scale(sx, 1.0);
                            }
                        }
                        else if (fname.equals("scaley"))
                        {
                            if (func.size() == 1 && isNumber(func.get(0)))
                            {
                                float sy = getNumber(func.get(0));
                                ret.scale(1.0, sy);
                            }
                        }
                        else if (fname.equals("skew"))
                        {
                            if (func.size() == 1 && func.get(0) instanceof TermAngle)
                            {
                                double ax = dec.getAngle((TermAngle) func.get(0));
                                ret.shear(Math.tan(ax), 0.0);
                            }
                            else if (func.size() == 2 && func.get(0) instanceof TermAngle && func.get(1) instanceof TermAngle)
                            {
                                double ax = dec.getAngle((TermAngle) func.get(0));
                                double ay = dec.getAngle((TermAngle) func.get(1));
                                ret.shear(Math.tan(ax), Math.tan(ay));
                            }
                        }
                        else if (fname.equals("skewx"))
                        {
                            if (func.size() == 1 && func.get(0) instanceof TermAngle)
                            {
                                double ax = dec.getAngle((TermAngle) func.get(0));
                                ret.shear(Math.tan(ax), 0.0);
                            }
                        }
                        else if (fname.equals("skewy"))
                        {
                            if (func.size() == 1 && func.get(0) instanceof TermAngle)
                            {
                                double ay = dec.getAngle((TermAngle) func.get(0));
                                ret.shear(0.0, Math.tan(ay));
                            }
                        }
                        else if (fname.equals("matrix"))
                        {
                            if (func.size() == 6)
                            {
                                double[] vals = new double[6];
                                boolean typesOk = true;
                                for (int i = 0; i < 6; i++)
                                {
                                    if (isNumber(func.get(i)))
                                        vals[i] = getNumber(func.get(i));
                                    else
                                        typesOk = false;
                                }
                                if (typesOk)
                                {
                                    ret.concatenate(new AffineTransform(vals));
                                }
                            }
                        }
                    }
                }
                ret.translate(-ox, -oy);
            }

            return ret;
        }
        else
            return null;
    }

    private boolean isNumber(Term<?> term)
    {
        return term instanceof TermNumber || term instanceof TermInteger;
    }

    private float getNumber(Term<?> term)
    {
        if (term instanceof TermNumber)
            return ((TermNumber) term).getValue();
        else
            return ((TermInteger) term).getValue();
    }

}


/**
 * @author XiongJie, Date: 13-10-23
 */
package net.happyonroad.util;

/** a simple log utils */
public class LogUtils {
    private LogUtils() {
    }

    /**
     * 输出一条Banner消息
     *
     * @param format    Banner消息格式，内嵌 {}作为占位符，语法应该与Slf4j的一样
     * @param alignLeft 左边对其的宽度
     * @param total     总宽度，在保证左对齐宽度的前提下，总的字符串宽度，如果内容超过，则进行截断
     * @param args      日志的参数
     * @return 可以输出的日志
     */
    public static String banner(String format, int alignLeft, int total, Object... args) {
        String message = format;
        for (Object arg : args) {
            message = message.replaceFirst("\\{\\}", arg == null ? "null" : arg.toString());
        }
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < alignLeft - 1; i++)
            builder.append("*");
        builder.append(" ");
        if (total > alignLeft + message.length()) {
            builder.append(message);
            builder.append(" ");
            for (int i = 0; i < total - 1 - alignLeft - message.length(); i++)
                builder.append("*");
        } else {
            builder.append(message.substring(0, total - alignLeft));
        }
        return builder.toString();
    }

    /**
     * 按照左对齐9个*号 + 1个空格，总长度为140进行默认banner格式化
     *
     * @param format Banner消息格式，内嵌 {}作为占位符，语法应该与Slf4j的一样
     * @param args   日志的参数
     * @return 可以输出的日志
     */
    public static String banner(String format, Object... args) {
        return banner(format, 8, 100, args);
    }
}


/**
 * @author XiongJie, Date: 13-8-28
 */
package net.happyonroad.component.core.support;

import net.happyonroad.component.core.ComponentResource;
import net.happyonroad.component.core.exception.InvalidComponentNameException;
import org.springframework.core.io.Resource;
import sun.net.www.protocol.jar.Handler;
import sun.net.www.protocol.jar.JarURLConnection;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

import static net.happyonroad.component.core.support.ComponentUtils.relativePath;

/**
 * 组件以Jar包形式，封闭状态运行
 */
public class ComponentJarResource extends ComponentResource {

    private final JarFile          file;

    public ComponentJarResource(Dependency dependency, String briefId) throws InvalidComponentNameException {
        this(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), briefId);
    }

    public ComponentJarResource(String groupId, String artifactId, String version, String briefId)
            throws InvalidComponentNameException {
        super(groupId, artifactId, version, briefId);
        try {
            URL manifestUrl = new URL(assemble("META-INF/MANIFEST.MF"));
            //借 connection对象生成 JarFile，这个jar file就被系统统一管理；
            // 而后直接返回的 new URL("jar:file:xxx!/yyy") 读取stream时，就会复用这个系统管理的JarFile
            JarURLConnection connection = new JarURLConnection(manifestUrl, new Handler());
            this.file = connection.getJarFile();
            this.manifest = connection.getManifest();
        } catch (IOException e) {
            throw new InvalidComponentNameException("Bad component: " + relativePath(getFileName()), e);
        }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // 额外扩展的对外方法
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public void close(){
        this.manifest.clear();
        this.manifest = null;
        // system shared jar file, do not close it
        //try{ this.file.close(); } catch (IOException ex){ /**/ }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // 重载父类方法
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////

    @Override
    public URL getURL() throws MalformedURLException {
        return new URL("component:" + getFileName());
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // 内部实现方法
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public InputStream getInputStream(String innerPath) throws IOException {
        JarEntry entry = file.getJarEntry(innerPath);
        if( entry == null )
            throw new IllegalArgumentException("Can't find " + innerPath + " from " + file.getName());
        //class loader可能还没有正确配置好,此时直接从file里面获取
        try {
            return super.getResource(innerPath).getInputStream();
        } catch (IOException e) {
            return file.getInputStream(entry);
        }
    }

    @Override
    public boolean exists(String relativePath) {
        try {
            return null != file.getJarEntry(relativePath);
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    public Resource[] getLocalResourcesUnder(String path) {
        Set<Resource> matches = new HashSet<Resource>();
        Enumeration<JarEntry> entries = file.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            if(entry.getName().startsWith(path)) {
                Resource resource = super.getResource(entry.getName());
                matches.add(resource);
            }
        }
        return matches.toArray(new Resource[matches.size()]);
    }

    @Override
    public Resource getLocalResourceUnder(String path) {
        if( isIndexed() && Arrays.binarySearch(indexes, path) >= 0){
            return super.getResource(path);
        }else{
            ZipEntry entry = file.getEntry(path);
            if( entry != null )
                return super.getResource(path);
        }
        return null;
    }

    @Override
    public URL getLocalResource(String path)  {
        final String url = assemble(path);
        try {
            return new URL(url);
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("Can't create jar url: " + url);
        }
    }

    // replace file protocol to component
    private String assemble(String path){
        return  "jar:component:" + getFileName() + "!/" + path;
    }
}


/**
 * @author XiongJie, Date: 13-11-11
 */
package net.happyonroad.component.container.feature;

import net.happyonroad.component.container.RepositoryScanner;
import net.happyonroad.component.core.Component;
import net.happyonroad.component.core.ComponentResource;
import net.happyonroad.component.core.Features;
import net.happyonroad.component.core.support.DefaultComponent;
import net.happyonroad.spring.context.AnnotationComponentApplicationContext;
import net.happyonroad.spring.context.XmlComponentApplicationContext;
import net.happyonroad.spring.event.ComponentLoadedEvent;
import net.happyonroad.spring.exception.ApplicationConfigurationException;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.annotation.AnnotationConfigRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.Resource;

import java.io.IOException;

import static org.springframework.context.ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS;

/**
 *
加载应用组件

 */
public class ApplicationFeatureResolver extends AbstractFeatureResolver {
    public static final String APP_CONFIG     = "App-Config";
    public static final String APP_REPOSITORY = "App-Repository";
    public static final String APP_XML        = "META-INF/application.xml";

    public ApplicationFeatureResolver() {
        super(30, 70);
    }

    @Override
    public String getName() {
        return Features.APPLICATION_FEATURE;
    }

    @Override
    public void applyDefaults(Component component) {
        super.applyDefaults(component);
        String appConfig = component.getManifestAttribute(APP_CONFIG);
        if (appConfig == null && readComponentDefaultConfig(component, "A").contains("A")) {
            appConfig = System.getProperty("default.app.config", "net.happyonroad.spring.config.DefaultAppConfig");
        }
        component.setManifestAttribute(APP_CONFIG, appConfig);
    }

    @Override
    public boolean hasFeature(Component component) {
        return byConfig(component) || byXml(component);
    }

    @Override
    public void resolve(Component component) throws IOException {
        logger.debug("Resolving {} {} feature", component, getName());
        ApplicationContext parent = resolveContext.getRootContext();
        AbstractApplicationContext context = null;
        if (byConfig(component)) {// By Config优先于By XML，如果两者都存在，则将会覆盖之
            context = resolveByConfig(component, parent);
        }
        if (context == null) {
            if (byXml(component)) {
                context = resolveByXml(component, parent);
            } else {
                throw new ApplicationContextException("Shouldn't get to this branch");
            }
        } else if (byXml(component)) {
            logger.warn("{} is configured by annotation and xml, the xml file will be ignored", component);
        }

        String env = System.getProperty("spring.env", "production");
        context.getEnvironment().setActiveProfiles(env);
        if (component.getScanners() != null) {
            for (RepositoryScanner scanner : component.getScanners()) {
                scanner.bind(context);
                scanner.scan();
            }
        }
        scanAppRepository(context, component);
        context.refresh();
        ((DefaultComponent) component).setApplication(context);
        context.start();
        resolveContext.registerFeature(component, getName(), context);
    }

    private void scanAppRepository(AbstractApplicationContext context, Component component) {
        String appRepository = component.getManifestAttribute(APP_REPOSITORY);
        if (StringUtils.isEmpty(appRepository)) {
            return;
        }
        int count;
        if (context instanceof AnnotationConfigRegistry) {
            int before = context.getBeanDefinitionCount();
            ((AnnotationConfigRegistry) context).scan(StringUtils.split(appRepository, CONFIG_LOCATION_DELIMITERS));
            count = context.getBeanDefinitionCount() - before;
        } else {
            ClassPathBeanDefinitionScanner scanner =
                    new ClassPathBeanDefinitionScanner((BeanDefinitionRegistry) context);
            scanner.setEnvironment(context.getEnvironment());
            scanner.setResourceLoader(context);
            scanner.setIncludeAnnotationConfig(false);
            count = scanner.scan(StringUtils.split(appRepository, CONFIG_LOCATION_DELIMITERS));
        }
        if (count > 0 && logger.isDebugEnabled()) {
            logger.debug("Scanned {} beans in {}", count, component.getDisplayName());
            String[] names = context.getBeanDefinitionNames();
            for (String name : names) {
                BeanDefinition definition = ((GenericApplicationContext) context).getBeanDefinition(name);
                if (definition instanceof ScannedGenericBeanDefinition) {
                    ScannedGenericBeanDefinition sgbd = (ScannedGenericBeanDefinition) definition;
                    Class<?> beanClass;
                    try {
                        beanClass = sgbd.resolveBeanClass(context.getClassLoader());
                    } catch (ClassNotFoundException e) {
                        continue;
                    }
                    if (beanClass != null) {
                        logger.debug("\t{}", beanClass.getName());
                    }
                }
            }
        }

    }

    /**
     * XML方式解析独立的应用程序包
     *
     * @param component 组件对象
     * @return 加载出来的应用程序上下文
     */
    protected AbstractApplicationContext resolveByXml(Component component,
                                                      ApplicationContext parent) {
        XmlComponentApplicationContext context = new XmlComponentApplicationContext(component, parent);
        try {
            Resource resource = component.getResourceLoader().getResource(APP_XML);
            context.load(resource);
        } catch (Exception e) {
            throw new ApplicationConfigurationException("Can't load context from application stream", e);
        }
        return context;
    }

    /**
     * Annotation方式解析独立的应用程序包
     *
     * @param component 组件对象
     * @return 加载出来的应用程序上下文
     */
    protected AbstractApplicationContext resolveByConfig(Component component,
                                                         ApplicationContext parent) {
        String appConfig = component.getManifestAttribute(APP_CONFIG);
        Class appConfigClass;
        try {
            appConfigClass = Class.forName(appConfig, true, component.getClassLoader());
        } catch (ClassNotFoundException e) {
            throw new ApplicationConfigurationException("The app config class: " + appConfig
                                                        + " can't be loaded by the component:" + component, e);
        }
        AnnotationComponentApplicationContext context = new AnnotationComponentApplicationContext(component, parent);
        context.register(appConfigClass);
        return context;
    }

    protected boolean byConfig(Component component) {
        String appConfig = component.getManifestAttribute(APP_CONFIG);
        return StringUtils.isNotBlank(appConfig);
    }

    protected boolean byXml(Component component) {
        ComponentResource resource = component.getResource();
        return resource.exists(APP_XML);
    }

    @Override
    public Object release(Component component) {
        AbstractApplicationContext context = (AbstractApplicationContext) super.release(component);
        if (context != null) {
            shutdownContext(context);
        } else {
            logger.error("Can't release loaded {} feature for: {}", getName(), component);
        }
        return context;
    }

    @Override
    public void afterResolve(Component component) {
        super.afterResolve(component);
        //向其内部所有组件发布通知，其已经被加载
        component.getApplication().publishEvent(new ComponentLoadedEvent(component));
    }

    protected void shutdownContext(AbstractApplicationContext context) {
        context.stop();
        context.close();
    }

}