/**
 * BoxNode.java
 *
 * Created on 2.6.2006, 11:39:46 by burgetr
 */
package org.fit.layout.cssbox;

import java.awt.Color;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import org.fit.cssbox.css.CSSUnits;
import org.fit.cssbox.layout.BlockReplacedBox;
import org.fit.cssbox.layout.Box;
import org.fit.cssbox.layout.ElementBox;
import org.fit.cssbox.layout.InlineReplacedBox;
import org.fit.cssbox.layout.ListItemBox;
import org.fit.cssbox.layout.ReplacedBox;
import org.fit.cssbox.layout.ReplacedContent;
import org.fit.cssbox.layout.ReplacedImage;
import org.fit.cssbox.layout.TextBox;
import org.fit.cssbox.layout.Viewport;
import org.fit.layout.impl.DefaultTreeNode;
import org.fit.layout.model.Border;
import org.fit.layout.model.Border.Side;
import org.fit.layout.model.Border.Style;
import org.fit.layout.model.ContentObject;
import org.fit.layout.model.Page;
import org.fit.layout.model.Rectangular;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import cz.vutbr.web.css.CSSProperty;
import cz.vutbr.web.css.NodeData;
import cz.vutbr.web.css.TermColor;
import cz.vutbr.web.css.CSSProperty.BorderStyle;

/**
 * A node of a tree of visual blocks.
 *
 * @author burgetr
 */
public class BoxNode extends DefaultTreeNode<org.fit.layout.model.Box> implements org.fit.layout.model.Box
{
    /** Overlapping threshold - the corners are considered to overlap if the boxes
     *  share more than OVERLAP pixels */
    private static final int OVERLAP = 2;

    /** Which percentage of the box area must be inside of another box in order
     * to consider it as a child box (from 0 to 1) */
    private static final double AREAP = 0.9;

    /** The CSSBox box that forms this node */
    protected Box box;

    /** The page this box belongs to. */
    protected Page page;

    /** Order in the box tree. It is assumed that the box tree obtained from the
     * rendering engine is sorted so that the siblings in the bottom are before the
     * siblings in front (i.e. in the drawing order). Usually, this corresponds
     * to the order in the document code. The order value doesn't correspond
     * to the order value of the source box because the box order values correspond
     * to the box creation order and not to the drawing order. */
    protected int order;

    /** The transformation that should be applied to the box */
    protected BoxTransform transform;

    /** The total bounds of the box node. Normally, the bounds are the same
     * as the content bounds. However, the BoxNode may be extended
     * in order to enclose all the overlapping boxes */
    protected Rectangular bounds;

    /** The total content bounds of the node. */
    protected Rectangular content;

    /** Visual bounds of the node. */
    protected Rectangular visual = null;

    /** Is the box separated by background */
    protected boolean backgroundSeparated = false;

    /** Efficient background color */
    protected Color efficientBackground = null;

    /** Potential nearest parent node in the box tree */
    public BoxNode nearestParent = null;

    /** Zoom relative to original box sizes */
    public float zoom;

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

    /**
     * Creates a new node containing a box.
     * @param box the contained box
     */
    public BoxNode(Box box, Page page, float zoom)
    {
        super(org.fit.layout.model.Box.class);
        this.box = box;
        this.page = page;
        this.zoom = zoom;
        //copy the bounds from the box
        if (box != null)
        {
            transform = new BoxTransform(box);
	        content = computeContentBounds();
	        bounds = new Rectangular(content); //later, this will be recomputed using recomputeBounds()
        }
    }

    @Override
    public int getId()
    {
        return getOrder();
    }

    /**
     * @return the order in the display tree
     */
    public int getOrder()
    {
        return order;
    }

    /**
     * @param order the display tree order to set
     */
    public void setOrder(int order)
    {
        this.order = order;
    }

    public boolean isRootNode()
    {
        //System.out.println(this + " => " + this.nearestParent);
        return nearestParent == null;
    }

    @Override
    public String toString()
    {
        Box box = getBox();
        String ret = "";
        if (efficientBackground != null)
            ret += (box != null && isVisuallySeparated()) ? "+" : "-";
        ret += order + ": ";
        if (box == null)
            ret += "- empty -";
        else if (box instanceof Viewport)
            ret += box.toString();
        else if (box instanceof ElementBox)
        {
            ElementBox elem = (ElementBox) box;
            ret += elem.getElement().getTagName();
            ret += " [" + elem.getElement().getAttribute("id") + "]";
            ret += " [" + elem.getElement().getAttribute("class") + "]";
            ret += " B" + getBounds().toString();
            ret += " V" + getVisualBounds().toString();
        }
        else if (box instanceof TextBox)
        {
            ret = ((TextBox) box).getText();
            ret += " (" + box.getAbsoluteBounds().x + ","
            			+ box.getAbsoluteBounds().y + ","
            			+ (box.getAbsoluteBounds().x + box.getAbsoluteBounds().width - 1) + ","
            			+ (box.getAbsoluteBounds().y + box.getAbsoluteBounds().height - 1) + ")";
        }
        else
            ret = "?: " + box.toString();

        return ret;
    }

    @Override
    public boolean equals(Object obj)
    {
        if (obj instanceof BoxNode)
        {
            return ((BoxNode) obj).getBox() == getBox();
        }
        else
            return false;
    }

    @Override
    public boolean isBackgroundSeparated()
    {
        return backgroundSeparated;
    }

    public void setBackgroundSeparated(boolean backgroundSeparated)
    {
        this.backgroundSeparated = backgroundSeparated;
    }

    /**
     * @return the efficient background color
     */
    public Color getEfficientBackground()
    {
        return efficientBackground;
    }

    /**
     * @param bgcolor the efficientBackground to set
     */
    public void setEfficientBackground(Color bgcolor)
    {
        this.efficientBackground = bgcolor;
    }

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

    /**
     * @return true if the box is visually separated by a border or
     * a different background color.
     */
    public boolean isVisuallySeparated()
    {
        Box box = getBox();

        //invisible boxes are not separated
        if (!isVisible())
            return false;
        //viewport is visually separated
        else if (box instanceof Viewport)
            return true;
        //non-empty text boxes are visually separated
        else if (box instanceof TextBox)
        {
            if (box.isEmpty())
                return false;
            else
                return true;
        }
        //replaced boxes are visually separated
        else if (box instanceof InlineReplacedBox || box instanceof BlockReplacedBox)
        {
            return true;
        }
        //list item boxes with a bullet
        else if (box instanceof ListItemBox)
        {
            return ((ListItemBox) box).hasVisibleBullet();
        }
        //other element boxes
        else
        {
            //check if separated by border -- at least one border needed
            if (getBorderCount() >= 1)
                return true;
            //check the background
            else if (isBackgroundSeparated())
                return true;
            return false;
        }
    }

    /**
     * Returns the minimal bounds of the box for enclosing all the contained boxes.
     * @return the minimal visual bounds
     */
    private Rectangular getMinimalVisualBounds()
    {
        final Box box = getBox();
        if (box instanceof TextBox)
            return new RectangularZ(box.getAbsoluteBounds().intersection(box.getClipBlock().getClippedContentBounds()), zoom);
        else if (box != null && box.isReplaced())
            return new RectangularZ(box.getMinimalAbsoluteBounds().intersection(box.getClipBlock().getClippedContentBounds()), zoom);
        else
        {
        	Rectangular ret = null;
            for (int i = 0; i < getChildCount(); i++)
            {
                BoxNode subnode = (BoxNode) getChildAt(i);
                Box sub = subnode.getBox();
                Rectangular sb = subnode.getVisualBounds();
                if (sub.isDisplayed() && subnode.isVisible() && sb.getWidth() > 0 && sb.getHeight() > 0)
                {
	                if (ret == null)
	                	ret = new Rectangular(sb);
	                else
	                	ret.expandToEnclose(sb);
                }
            }
            //if nothing has been found return an empty rectangle at the top left corner
            if (ret == null)
            {
                Rectangle b = box.getAbsoluteBounds().intersection(box.getClipBlock().getClippedContentBounds());
            	return new RectangularZ(b.x, b.y, zoom);
            }
            else
            	return ret;
        }
    }

    /**
     * Returns the bounds of the box as they visually appear to the user.
     * @return the visual bounds
     */
    @Override
    public Rectangular getVisualBounds()
    {
        if (visual == null)
            visual = computeVisualBounds();
        return visual;
    }

    private Rectangular computeVisualBounds()
    {
        Box box = getBox();
        Rectangular ret = null;

        if (box instanceof Viewport)
        {
            ret = new RectangularZ(((Viewport) box).getClippedBounds(), zoom);
        }
        else if (box instanceof ElementBox)
        {
            ElementBox elem = (ElementBox) box;
            //one border only -- the box represents the border only
            if (getBorderCount() == 1 && !isBackgroundSeparated())
            {
            	Rectangular b = new RectangularZ(elem.getAbsoluteBorderBounds().intersection(elem.getClipBlock().getClippedContentBounds()), zoom); //clipped absolute bounds
            	if (hasTopBorder())
            		ret = new Rectangular(b.getX1(), b.getY1(), b.getX2(), b.getY1() + zoom(elem.getBorder().top) - 1);
            	else if (hasBottomBorder())
            		ret = new Rectangular(b.getX1(), b.getY2() - zoom(elem.getBorder().bottom) + 1, b.getX2(), b.getY2());
            	else if (hasLeftBorder())
            		ret = new Rectangular(b.getX1(), b.getY1(), b.getX1() + zoom(elem.getBorder().left) - 1, b.getY2());
            	else if (hasRightBorder())
            		ret = new Rectangular(b.getX2() - zoom(elem.getBorder().right) + 1, b.getY1(), b.getX2(), b.getY2());
            }
            //at least two borders or a border and background - take the border bounds
            else if (getBorderCount() >= 2 || (getBorderCount() == 1 && isBackgroundSeparated()))
            {
                ret = new RectangularZ(elem.getAbsoluteBorderBounds().intersection(elem.getClipBlock().getClippedContentBounds()), zoom);
            }

            //consider the background if different from the parent
            if (isBackgroundSeparated())
            {
                Rectangular bg = new RectangularZ(elem.getAbsoluteBackgroundBounds().intersection(elem.getClipBlock().getClippedContentBounds()), zoom);
                if (ret == null)
                    ret = bg;
                else
                    ret.expandToEnclose(bg);
            }
            //no visual separators, consider the contents
            else
            {
                Rectangular cont  = getMinimalVisualBounds();
                if (ret == null)
                    ret = cont;
                else
                    ret.expandToEnclose(cont);
            }
        }
        else //not an element
            ret = getMinimalVisualBounds();

        return ret;
    }

    /**
     * Re-computes the visual bounds of the whole subtree.
     */
    public void recomputeVisualBounds()
    {
        for (int i = 0; i < getChildCount(); i++)
            ((BoxNode) getChildAt(i)).recomputeVisualBounds();
        visual = computeVisualBounds();
    }

    /**
     * Recomputes the total bounds of the whole subtree. The bounds of each box will
     * correspond to its visual bounds. If the child boxes exceed the parent box,
     * the parent box bounds will be expanded accordingly.
     */
    public void recomputeBounds()
    {
        bounds = new Rectangular(visual);
        for (int i = 0; i < getChildCount(); i++)
        {
            BoxNode child = (BoxNode) getChildAt(i);
            child.recomputeBounds();
            expandToEnclose(child);
        }
    }

    /**
     * Computes node the content bounds. They correspond to the background bounds
     * however, when a border is present, it is included in the contents. Moreover,
     * the box is clipped by its clipping box.
     */
    private Rectangular computeContentBounds()
    {
        Box box = getBox();
        Rectangular ret = null;

        if (box instanceof Viewport)
        {
            ret = new RectangularZ(((Viewport) box).getClippedBounds(), zoom);
        }
        else if (box instanceof ElementBox)
        {
            ElementBox elem = (ElementBox) box;
            //at least one border - take the border bounds
            //TODO: when only one border is present, we shouldn't take the whole border box?
            if (elem.getBorder().top > 0 || elem.getBorder().left > 0 ||
                elem.getBorder().bottom > 0 || elem.getBorder().right > 0)
            {
                ret = new RectangularZ(elem.getAbsoluteBorderBounds(), zoom);
            }
            //no border
            else
            {
                ret = new RectangularZ(elem.getAbsoluteBackgroundBounds(), zoom);
            }
        }
        else //not an element - return the whole box
            ret = new RectangularZ(box.getAbsoluteBounds(), zoom);

        //clip with the clipping bounds
        if (box.getClipBlock() != null)
        {
            Rectangular clip = new RectangularZ(box.getClipBlock().getClippedContentBounds(), zoom);
            ret = ret.intersection(clip);
        }

        return ret;
    }

    /**
     * @return the number of defined borders for the box
     */
    public int getBorderCount()
    {
        int bcnt = 0;
        if (hasTopBorder()) bcnt++;
        if (hasBottomBorder()) bcnt++;
        if (hasLeftBorder()) bcnt++;
        if (hasRightBorder()) bcnt++;
        return bcnt;
    }

    /**
     * @return true if the box has a top border
     */
    @Override
    public boolean hasTopBorder()
    {
        Box box = getBox();
        if (box instanceof ElementBox && ((ElementBox) box).getBorder().top > 0)
            return true;
        else
            return false;
    }

    /**
     * Obtains the top border of the box
     * @return the width of the border or 0 when there is no border
     */
    @Override
    public int getTopBorder()
    {
        Box box = getBox();
        if (box instanceof ElementBox)
            return ((ElementBox) box).getBorder().top;
        else
            return 0;
    }

    /**
     * @return true if the box has a bottom border
     */
    @Override
    public boolean hasBottomBorder()
    {
        Box box = getBox();
        if (box instanceof ElementBox && ((ElementBox) box).getBorder().bottom > 0)
            return true;
        else
            return false;
    }

    /**
     * Obtains the bottom border of the box
     * @return the width of the border or 0 when there is no border
     */
    @Override
    public int getBottomBorder()
    {
        Box box = getBox();
        if (box instanceof ElementBox)
            return ((ElementBox) box).getBorder().bottom;
        else
            return 0;
    }

    /**
     * @return true if the box has a left border
     */
    @Override
    public boolean hasLeftBorder()
    {
        Box box = getBox();
        if (box instanceof ElementBox && ((ElementBox) box).getBorder().left > 0)
            return true;
        else
            return false;
    }

    /**
     * Obtains the left border of the box
     * @return the width of the border or 0 when there is no border
     */
    @Override
    public int getLeftBorder()
    {
        Box box = getBox();
        if (box instanceof ElementBox)
            return ((ElementBox) box).getBorder().left;
        else
            return 0;
    }

    /**
     * @return true if the box has a right border
     */
    @Override
    public boolean hasRightBorder()
    {
        Box box = getBox();
        if (box instanceof ElementBox && ((ElementBox) box).getBorder().right > 0)
            return true;
        else
            return false;
    }

    /**
     * Obtains the right border of the box
     * @return the width of the border or 0 when there is no border
     */
    @Override
    public int getRightBorder()
    {
        Box box = getBox();
        if (box instanceof ElementBox)
            return ((ElementBox) box).getBorder().right;
        else
            return 0;
    }

    @Override
    public Border getBorderStyle(Side side)
    {
        Box box = getBox();
        if (box instanceof ElementBox)
        {
            final NodeData style = ((ElementBox) box).getStyle();
            TermColor tclr = style.getValue(TermColor.class, "border-"+side+"-color");
            CSSProperty.BorderStyle bst = style.getProperty("border-"+side+"-style");
            if (bst == null)
                bst = BorderStyle.NONE;

            Color clr = null;
            if (tclr != null)
                clr = CSSUnits.convertColor(tclr.getValue());
            if (clr == null)
            {
                clr = box.getVisualContext().getColor();
                if (clr == null)
                    clr = Color.BLACK;
            }

            int rwidth = 0;
            switch (side)
            {
                case BOTTOM:
                    rwidth = getBottomBorder();
                    break;
                case LEFT:
                    rwidth = getLeftBorder();
                    break;
                case RIGHT:
                    rwidth = getRightBorder();
                    break;
                case TOP:
                    rwidth = getTopBorder();
                    break;
            }

            Border.Style rstyle;
            switch (bst)
            {
                case NONE:
                case HIDDEN:
                    rstyle = Style.NONE;
                    break;
                case DASHED:
                    rstyle = Style.DASHED;
                    break;
                case DOTTED:
                    rstyle = Style.DOTTED;
                    break;
                case DOUBLE:
                    rstyle = Style.DOUBLE;
                    break;
                default:
                    rstyle = Style.SOLID;
                    break;
            }

            return new Border(rwidth, rstyle, clr);
        }
        else
            return new Border();
    }

    /**
     * Get the effective text color. If the text color is set, it is returned.
     * When the color is not set, the parent boxes are considered.
     * @return the background color string
     */
    public String getEfficientColor()
    {
        Box box = getBox();
        do
        {
            if (box instanceof ElementBox)
            {
                String color = ((ElementBox) box).getStylePropertyValue("color");
                if (!color.equals(""))
                    return color;
            }
            box = box.getParent();
        } while (box != null);
        return "";
    }

    /**
     * Obtains the text color of the first box in the area.
     * @return The color.
     */
    public Color getStartColor()
    {
    	return getBox().getVisualContext().getColor();
    }

    /**
     * Checks if the box text is visible (it does not contain spaces only). This is not equivalent to isWhitespace() because
     * non-beraking spaces are not whitespace characters but they are not visible.
     * @return <true> if the box text consists of space characters only
     */
    public boolean containsVisibleTextString()
    {
        String s = getText();
        for (int i = 0; i < s.length(); i++)
        {
            char ch = s.charAt(i);
            if (!Character.isSpaceChar(ch))
                return true;
        }
        return false;
    }

    /**
     * Checks whether the box is inside of the visible area and the text is visible and its color is different from the background
     * @return true if the box is visible
     */
    @Override
    public boolean isVisible()
    {
        return !getVisualBounds().isEmpty();
    }

    /**
     * Returns the declared background color of the element or null when transparent.
     */
    @Override
    public Color getBackgroundColor()
    {
        if (getBox() instanceof ElementBox)
            return ((ElementBox) getBox()).getBgcolor();
        else
            return null;
    }

    public void applyTransform(BoxTransform trans)
    {
        if (!trans.isEmpty())
        {
            System.out.println("Old: " + bounds);
            bounds = trans.transformRect(bounds);
            System.out.println("New: " + bounds);
            if (content != null)
                content = trans.transformRect(content);
            if (visual != null)
                visual = trans.transformRect(visual);
        }
    }

    public void applyTransformRecursively(BoxTransform trans)
    {
        for (int i = 0; i < getChildCount(); i++)
            ((BoxNode) getChildAt(i)).applyTransformRecursively(trans);
    }

    public void applyTransforms()
    {
        //apply our transform recursively
        if (!transform.isEmpty())
        {
            applyTransform(transform);
            applyTransformRecursively(transform);
        }
        //apply their transforms to the children, remove the invisible ones
        //TODO this is a temporaty hack, it should be solved better
        List<BoxNode> toRemove = new ArrayList<>();
        for (int i = 0; i < getChildCount(); i++)
        {
            BoxNode child = (BoxNode) getChildAt(i);
            child.applyTransforms();
            if (!this.getVisualBounds().intersects(child.getVisualBounds()))
                toRemove.add(child);
        }
        if (!toRemove.isEmpty())
        {
            for (BoxNode child : toRemove)
                removeChild(child);
            recomputeVisualBounds();
            visual = transform.transformRect(visual);
        }
    }

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

    /**
     * @return the contained box
     */
    public Box getBox()
    {
        return box;
    }

    /**
	 * @return the total bounds of the node
	 */
    @Override
	public Rectangular getBounds()
	{
		return bounds;
	}

	/**
	 * @return the content bounds of the node
	 */
    @Override
	public Rectangular getContentBounds()
	{
		return content;
	}

	/**
     * Checks if another node is located inside the visual bounds of this box.
     * @param childNode the node to check
     * @return true if the child node is completely inside this node, false otherwise
     */
    public boolean visuallyEncloses(BoxNode childNode)
    {
        int cx1 = childNode.getVisualBounds().getX1();
        int cy1 = childNode.getVisualBounds().getY1();
        int cx2 = childNode.getVisualBounds().getX2();
        int cy2 = childNode.getVisualBounds().getY2();
        int px1 = getVisualBounds().getX1();
        int py1 = getVisualBounds().getY1();
        int px2 = getVisualBounds().getX2();
        int py2 = getVisualBounds().getY2();

        /*if (this.toString().contains("pBody") && childNode.toString().contains("mediawiki"))
            System.out.println(childNode + " inside of " + this);
        if (childNode.toString().contains("www-lupa-cz") && this.toString().contains("[page]"))
            System.out.println(childNode + " inside of " + this);*/
        /*if (this.getOrder() == 70 && childNode.getOrder() == 74)
            System.out.println("jo!");*/

        //check how many corners of the child are inside enough (with some overlap)
        int ccnt = 0;
        if (cx1 >= px1 + OVERLAP && cx1 <= px2 - OVERLAP &&
            cy1 >= py1 + OVERLAP && cy1 <= py2 - OVERLAP) ccnt++; //top left
        if (cx2 >= px1 + OVERLAP && cx2 <= px2 - OVERLAP &&
            cy1 >= py1 + OVERLAP && cy1 <= py2 - OVERLAP) ccnt++; //top right
        if (cx1 >= px1 + OVERLAP && cx1 <= px2 - OVERLAP &&
            cy2 >= py1 + OVERLAP && cy2 <= py2 - OVERLAP) ccnt++; //bottom left
        if (cx2 >= px1 + OVERLAP && cx2 <= px2 - OVERLAP &&
            cy2 >= py1 + OVERLAP && cy2 <= py2 - OVERLAP) ccnt++; //bottom right
        //check how many corners of the child are inside the parent exactly
        int xcnt = 0;
        if (cx1 >= px1 && cx1 <= px2 &&
            cy1 >= py1 && cy1 <= py2) xcnt++; //top left
        if (cx2 >= px1 && cx2 <= px2 &&
            cy1 >= py1 && cy1 <= py2) xcnt++; //top right
        if (cx1 >= px1 && cx1 <= px2 &&
            cy2 >= py1 && cy2 <= py2) xcnt++; //bottom left
        if (cx2 >= px1 && cx2 <= px2 &&
            cy2 >= py1 && cy2 <= py2) xcnt++; //bottom right
        //and reverse direction - how many corners of the parent are inside of the child
        int rxcnt = 0;
        if (px1 >= cx1 && px1 <= cx2 &&
            py1 >= cy1 && py1 <= cy2) rxcnt++; //top left
        if (px2 >= cx1 && px2 <= cx2 &&
            py1 >= cy1 && py1 <= cy2) rxcnt++; //top right
        if (px1 >= cx1 && px1 <= cx2 &&
            py2 >= cy1 && py2 <= cy2) rxcnt++; //bottom left
        if (px2 >= cx1 && px2 <= cx2 &&
            py2 >= cy1 && py2 <= cy2) rxcnt++; //bottom right
        //shared areas
        int shared = getVisualBounds().intersection(childNode.getVisualBounds()).getArea();
        double sharedperc = (double) shared / childNode.getBounds().getArea();

        //no overlap
        if (xcnt == 0)
        	return false;
        //fully overlapping or over a corner - the order decides
        else if ((cx1 == px1 && cy1 == py1 && cx2 == px2 && cy2 == py2) //full overlap
        	     || (ccnt == 1 && xcnt <= 1)) //over a corner
        	return this.getOrder() < childNode.getOrder() && sharedperc >= AREAP;
        //fully inside
        else if (xcnt == 4)
            return true;
        //partly inside (at least two corners)
        else if (xcnt >= 2)
        {
            if (rxcnt == 4) //reverse relation - the child contains the parent
                return false;
            else //child partly inside the parent
                return this.getOrder() < childNode.getOrder() && sharedperc >= AREAP;
        }
        //not inside
        else
            return false;
    }

    /**
     * Checks if another node is fully located inside the content bounds of this box.
     * @param childNode the node to check
     * @return true if the child node is completely inside this node, false otherwise
     */
    public boolean visuallyEncloses1(BoxNode childNode)
    {
        int cx1 = childNode.getVisualBounds().getX1();
        int cy1 = childNode.getVisualBounds().getY1();
        int cx2 = childNode.getVisualBounds().getX2();
        int cy2 = childNode.getVisualBounds().getY2();
        int px1 = getVisualBounds().getX1();
        int py1 = getVisualBounds().getY1();
        int px2 = getVisualBounds().getX2();
        int py2 = getVisualBounds().getY2();

        //check how many corners of the child are inside the parent exactly
        int xcnt = 0;
        if (cx1 >= px1 && cx1 <= px2 &&
            cy1 >= py1 && cy1 <= py2) xcnt++; //top left
        if (cx2 >= px1 && cx2 <= px2 &&
            cy1 >= py1 && cy1 <= py2) xcnt++; //top right
        if (cx1 >= px1 && cx1 <= px2 &&
            cy2 >= py1 && cy2 <= py2) xcnt++; //bottom left
        if (cx2 >= px1 && cx2 <= px2 &&
            cy2 >= py1 && cy2 <= py2) xcnt++; //bottom right

        /*if (childNode.toString().contains("globalWrapper") && this.toString().contains("mediawiki"))
            System.out.println("jo!");*/

        if ((cx1 == px1 && cy1 == py1 && cx2 == px2 && cy2 == py2)) //exact overlap
           return this.getOrder() < childNode.getOrder();
        else
            return xcnt == 4;
    }

    /**
     * Checks if another node is fully located inside the content bounds of this box.
     * @param childNode the node to check
     * @return true if the child node is completely inside this node, false otherwise
     */
    public boolean contentEncloses(BoxNode childNode)
    {
    	//System.out.println(childNode + " => " + childNode.getVisualBounds());
        int cx1 = childNode.getContentBounds().getX1();
        int cy1 = childNode.getContentBounds().getY1();
        int cx2 = childNode.getContentBounds().getX2();
        int cy2 = childNode.getContentBounds().getY2();
        int px1 = getContentBounds().getX1();
        int py1 = getContentBounds().getY1();
        int px2 = getContentBounds().getX2();
        int py2 = getContentBounds().getY2();

        //check how many corners of the child are inside the parent exactly
        int xcnt = 0;
        if (cx1 >= px1 && cx1 <= px2 &&
            cy1 >= py1 && cy1 <= py2) xcnt++; //top left
        if (cx2 >= px1 && cx2 <= px2 &&
            cy1 >= py1 && cy1 <= py2) xcnt++; //top right
        if (cx1 >= px1 && cx1 <= px2 &&
            cy2 >= py1 && cy2 <= py2) xcnt++; //bottom left
        if (cx2 >= px1 && cx2 <= px2 &&
            cy2 >= py1 && cy2 <= py2) xcnt++; //bottom right

        if ((cx1 == px1 && cy1 == py1 && cx2 == px2 && cy2 == py2)) //exact overlap
           return this.getOrder() < childNode.getOrder();
        else
            return xcnt == 4;
    }

    /**
     * Expands the box node in order to fully enclose another box
     */
    public void expandToEnclose(BoxNode child)
    {
    	bounds.expandToEnclose(child.getBounds());
    }

    /**
     * Takes a list of nodes and selects the nodes that are located directly inside
     * of this node's box. The {@code nearestParent} of the selected boxes is set to this box.
     * @param list the list of nodes to test
     * @param full when set to true, all the nodes within the box content bounds are considered.
     *          Otherwise, only the boxes within the visual bounds are considered.
     */
    public void markNodesInside(Vector<BoxNode> list, boolean full)
    {
        for (Iterator<BoxNode> it = list.iterator(); it.hasNext();)
        {
            BoxNode node = it.next();
            if (full)
            {
                if (node != this
                    && this.contentEncloses(node)
                    && (node.isRootNode() || !this.contentEncloses(node.nearestParent)))
                {
                    node.nearestParent = this;
                }
            }
            else
            {
                if (node != this
                        && this.visuallyEncloses(node)
                        && (node.isRootNode() || !this.visuallyEncloses(node.nearestParent)))
                {
                    node.nearestParent = this;
                }
            }
        }
    }

    /**
     * Takes a list of nodes and selects the nodes whose parent box is identical to this node's box.
     * The {@code nearestParent} of the selected boxes is set to this box node.
     * @param list the list of nodes to test
     */
    public void markChildNodes(Vector<BoxNode> list)
    {
        final Box thisBox = this.getBox();
        for (Iterator<BoxNode> it = list.iterator(); it.hasNext();)
        {
            BoxNode node = it.next();
            if (node != this && node.getBox().getParent() == thisBox)
                node.nearestParent = this;
        }
    }

    /**
     * Goes through the parent's children, takes all the nodes that are inside of this node
     * and makes them the children of this node. Then, recursively calls the children to take
     * their nodes.
     */
	public void takeChildren(Vector<BoxNode> list)
    {
        for (Iterator<BoxNode> it = list.iterator(); it.hasNext();)
        {
            BoxNode node = it.next();
            if (node.nearestParent.equals(this))
            {
                appendChild(node);
                it.remove();
            }
        }

        //let the children take their children
        for (int i = 0; i < getChildCount(); i++)
            ((BoxNode) getChildAt(i)).takeChildren(list);
    }

    /**
     * Removes the node from the tree. Clears the parent and removes all the child
     * node.
     */
    public void removeFromTree()
    {
        nearestParent = null;
        //setParent(null);
        removeAllChildren();
    }

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

    /**
     * @return all the text contained in this box and its subboxes.
     * Contents of the individual text boxes are separated by spaces.
     */
    @Override
    public String getText()
    {
        return recursiveGetText(this);
    }

    private String recursiveGetText(BoxNode root)
    {
        Box box = root.getBox();
        if (box instanceof TextBox)
            return ((TextBox) box).getText();
        else
        {
            String ret = "";
            for (int i = 0; i < root.getChildCount(); i++)
            {
                if (ret.trim().length() > 0)
                    ret += " ";
                ret = ret + recursiveGetText((BoxNode) root.getChildAt(i)).trim();
            }
            return ret;
        }
    }

    @Override
    public String getOwnText()
    {
        final Box box = getBox();
        if (box instanceof TextBox)
            return ((TextBox) box).getText();
        else
            return null;
    }

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

    @Override
    public Page getPage()
    {
        return page;
    }

    @Override
    public float getUnderline()
    {
        return getBox().getVisualContext().getTextDecoration().contains(CSSProperty.TextDecoration.UNDERLINE) ? 1.0f : 0.0f;
    }

    @Override
    public float getLineThrough()
    {
        return getBox().getVisualContext().getTextDecoration().contains(CSSProperty.TextDecoration.LINE_THROUGH) ? 1.0f : 0.0f;
    }

    @Override
    public float getFontSize()
    {
        return getBox().getVisualContext().getFont().getSize2D();
    }

    @Override
    public float getFontStyle()
    {
        return getBox().getVisualContext().getFont().isItalic() ? 1.0f : 0.0f;
    }

    @Override
    public float getFontWeight()
    {
        return getBox().getVisualContext().getFont().isBold() ? 1.0f : 0.0f;
    }

    @Override
    public int getX1()
    {
        return getVisualBounds().getX1();
    }

    @Override
    public int getY1()
    {
        return getVisualBounds().getY1();
    }

    @Override
    public int getX2()
    {
        return getVisualBounds().getX2();
    }

    @Override
    public int getY2()
    {
        return getVisualBounds().getY2();
    }

    @Override
    public int getWidth()
    {
        return getVisualBounds().getWidth();
    }

    @Override
    public int getHeight()
    {
        return getVisualBounds().getHeight();
    }

    @Override
    public void move(int xofs, int yofs)
    {
        getBounds().move(xofs, yofs);
        getContentBounds().move(xofs, yofs);
        getVisualBounds().move(xofs, yofs);
        for (org.fit.layout.model.Box child : getChildren())
        {
            ((BoxNode) child).move(xofs, yofs);
        }
    }

    @Override
    public Color getColor()
    {
        return getBox().getVisualContext().getColor();
    }

    @Override
    public String getFontFamily()
    {
        return getBox().getVisualContext().getFont().getName();
    }

    @Override
    public ContentObject getContentObject()
    {
        if (getBox().isReplaced())
        {
            ReplacedContent content = ((ReplacedBox) getBox()).getContentObj();
            if (content instanceof ReplacedImage)
                return new ContentImageImpl((ReplacedImage) content);
            else
                return null;
        }
        else
            return null;
    }

    @Override
    public Type getType()
    {
        if (getBox().isReplaced())
            return Type.REPLACED_CONTENT;
        else if (getBox() instanceof TextBox)
            return Type.TEXT_CONTENT;
        else
            return Type.ELEMENT;
    }

    public Node getDOMNode()
    {
        return getBox().getNode();
    }

    @Override
    public int getSourceNodeId()
    {
        return System.identityHashCode(getBox().getNode());
    }

    @Override
    public String getTagName()
    {
        final Node node = getDOMNode();
        if (node != null && node.getNodeType() == Node.ELEMENT_NODE)
            return ((Element) node).getTagName().toLowerCase();
        else
            return null;
    }

    @Override
    public String getAttribute(String name)
    {
        final Node node = getDOMNode();
        if (node != null)
        {
            if ("href".equals(name))
                return getAncestorAttribute(node, "a", name);
            else
                return getElementAttribute(node, name);
        }
        else
            return null;
    }

    @Override
    public Map<String, String> getAttributes()
    {
        final Node node = getDOMNode();
        NamedNodeMap map = null;
        if (node.getNodeType() == Node.ELEMENT_NODE)
        {
            map = node.getAttributes();
        }
        else if (node.getNodeType() == Node.TEXT_NODE) //text nodes -- try parent //TODO how to propagate from ancestors correctly?
        {
            final Node pnode = node.getParentNode();
            if (pnode != null && pnode.getNodeType() == Node.ELEMENT_NODE)
            {
                map = pnode.getAttributes();
            }
        }

        Map<String, String> ret = new HashMap<>((map == null) ? 1 : (map.getLength() + 1));
        if (map != null) //store the attributes found
        {
            for (int i = 0; i < map.getLength(); i++)
            {
                final Node attr = map.item(i);
                ret.put(attr.getNodeName(), attr.getNodeValue());
            }
        }
        //eventually add the href value (which may be inherited from top)
        if (!ret.containsKey("href"))
        {
            String href = getAncestorAttribute(node, "a", "href");
            if (href != null)
                ret.put("href", href);
        }
        return ret;
    }

    protected String getElementAttribute(Node node, String attrName)
    {
        if (node.getNodeType() == Node.ELEMENT_NODE)
        {
            final Element el = (Element) node;
            if (el.hasAttribute(attrName))
                return el.getAttribute(attrName);
            else
                return null;
        }
        else if (node.getNodeType() == Node.TEXT_NODE) //text nodes -- try parent //TODO how to propagate from ancestors correctly?
        {
            final Node pnode = node.getParentNode();
            if (pnode != null && pnode.getNodeType() == Node.ELEMENT_NODE)
            {
                final Element parent = (Element) pnode;
                if (parent.hasAttribute(attrName))
                    return parent.getAttribute(attrName);
                else
                    return null;
            }
            else
                return null;
        }
        else
            return null;
    }

    protected String getAncestorAttribute(Node node, String elementName, String attrName)
    {
        Node cur = node;
        //find the parent with the given name
        while (cur.getNodeType() != Node.ELEMENT_NODE || !elementName.equals(cur.getNodeName()))
        {
            cur = cur.getParentNode();
            if (cur == null)
                return null;
        }
        //read the attribute
        final Element el = (Element) cur;
        if (el.hasAttribute(attrName))
            return el.getAttribute(attrName);
        else
            return null;
    }

    @Override
    public DisplayType getDisplayType()
    {
        Box box = getBox();
        if (box instanceof ElementBox)
        {
            CSSProperty.Display display = ((ElementBox) box).getDisplay();
            if (display == null)
                return DisplayType.BLOCK; //e.g. the viewport has no display value
            switch (display)
            {
                case BLOCK:
                    return DisplayType.BLOCK;
                case INLINE:
                    return DisplayType.INLINE;
                case INLINE_BLOCK:
                    return DisplayType.INLINE_BLOCK;
                case INLINE_TABLE:
                    return DisplayType.INLINE_TABLE;
                case LIST_ITEM:
                    return DisplayType.LIST_ITEM;
                case NONE:
                    return DisplayType.NONE;
                case RUN_IN:
                    return DisplayType.RUN_IN;
                case TABLE:
                    return DisplayType.TABLE;
                case TABLE_CAPTION:
                    return DisplayType.TABLE_CAPTION;
                case TABLE_CELL:
                    return DisplayType.TABLE_CELL;
                case TABLE_COLUMN:
                    return DisplayType.TABLE_COLUMN;
                case TABLE_COLUMN_GROUP:
                    return DisplayType.TABLE_COLUMN_GROUP;
                case TABLE_FOOTER_GROUP:
                    return DisplayType.TABLE_FOOTER_GROUP;
                case TABLE_HEADER_GROUP:
                    return DisplayType.TABLE_HEADER_GROUP;
                case TABLE_ROW:
                    return DisplayType.TABLE_ROW;
                case TABLE_ROW_GROUP:
                    return DisplayType.TABLE_ROW_GROUP;
                default:
                    return DisplayType.BLOCK; //this should not happen
            }
        }
        else
            return null;
    }

    @Override
    public Rectangular getSubstringBounds(int startPos, int endPos)
    {
        Box box = getBox();
        if (box instanceof TextBox)
        {
            Rectangular ret = new Rectangular(getVisualBounds());
            int origin = ret.getX1();
            int startOfs = ((TextBox) box).getCharOffsetX(startPos);
            int endOfs = ((TextBox) box).getCharOffsetX(endPos);
            ret.setX1(origin + startOfs);
            ret.setX2(origin + endOfs);
            return ret;
        }
        else
            return null;
    }

    private int zoom(int src)
    {
        return Math.round(src * zoom);
    }

}


/**
 * @author XiongJie, Date: 13-10-29
 */
package net.happyonroad.spring.support;

import net.happyonroad.spring.service.MutableServiceRegistry;
import net.happyonroad.spring.exception.ServiceConfigurationException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * 缺省的服务注册表
 */
@SuppressWarnings("unchecked")
public class DefaultServiceRegistry implements MutableServiceRegistry {
    protected Logger                          logger   = LoggerFactory.getLogger(getClass());
    private   Map<Class, Map<String, Object>> services = new HashMap<Class, Map<String, Object>>();

    ////////////////////////////////////////////////////////////////////////
    // Mutable Service Registry的实现
    ////////////////////////////////////////////////////////////////////////

    @Override
    public <T> void register(Class<T> interfaceClass, T service) {
        register(interfaceClass, service, DEFAULT_HINT);
    }

    @Override
    public <T> void register(Class<T> interfaceClass, T service, String hint) {
        String serviceName = service.getClass().getSimpleName();
        logger.info("Register {} by {} with hint {}", interfaceClass.getName(), serviceName, hint);
        Map<String, Object> instances = services.get(interfaceClass);
        if (instances == null) {
            instances = new HashMap<String, Object>(2);
            services.put(interfaceClass, instances);
        }
        instances.put(hint, service);
    }

    @Override
    public <T> void register(Class[] interfaceClasses, T service, String hint) {
        for (Class interfaceClass : interfaceClasses) {
            register(interfaceClass, service, hint);
        }
    }

    @Override
    public <T> T unRegister(Class<T> interfaceClass, String hint) {
        List<T> removes = new ArrayList<T>();
        Set<Class> keys = services.keySet();
        for (Class key : keys) {
            if (interfaceClass.isAssignableFrom(key)) {
                Map<String, Object> theMap = services.get(key);
                if (theMap != null) {
                    T removed = (T) theMap.remove(hint);
                    if (removed != null) removes.add(removed);
                }
            }
        }
        if (removes.size() == 1) {
            return removes.get(0);
        } else if (removes.isEmpty()) {
            return null;
        } else {
            logger.warn("I have unRegister multiple service {} with hint {}, {}",
                        interfaceClass, hint, StringUtils.join(removes, ","));
            return removes.get(0);
        }
    }

    @Override
    public <T> Map<String, T> unRegister(Class<T> interfaceClass) {
        Set<Class> keys = services.keySet();
        Map<String, T> removes = new HashMap<String, T>();
        for (Class key : keys) {
            if (interfaceClass.isAssignableFrom(key)) {
                Map<String, T> theMap = (Map<String, T>) services.remove(key);
                if (theMap != null) {
                    removes.putAll(theMap);
                }
            }
        }
        return removes;
    }

    ////////////////////////////////////////////////////////////////////////
    // Service Registry的实现
    ////////////////////////////////////////////////////////////////////////

    @Override
    public <T> T getService(Class<T> requiredType) {
        return getService(requiredType, ANY_HINT);
    }

    @Override
    public <T> T getService(Class<T> requiredType, String hint) {
        Map<String, Object> map = services.get(requiredType);
        T service = getServiceFromMap(map, hint);
        if (service == null) {
            //如果精确查找找不到，则进行模糊匹配，找到第一个算数
            map = digMaps(requiredType);
            service = getServiceFromMap(map, hint);
        }
        return service;
    }

    protected <T> T getServiceFromMap(Map<String, Object> map, String hint) {
        if (map == null || map.isEmpty()) return null;
        if (ANY_HINT.equals(hint)) {
            return (T) map.values().iterator().next();
        }
        return (T) map.get(hint);
    }

    @Override
    public <T> T getService(Class[] requiredTypes, String hint) {
        Set<T> found = new HashSet<T>();
        for (Class type : requiredTypes) {
            T service = (T) getService(type, hint);
            found.add(service);
        }
        if (found.size() != 1) {
            String types = StringUtils.join(requiredTypes, ",");
            throw new ServiceConfigurationException("The service with hint = " + hint +
                                                    " is not exported with multiple interfaces: " + types);
        }
        return found.iterator().next();
    }

    @Override
    public <T> Map<String, T> getServices(Class<T> requiredType) {
        Map<String, Object> map = digMaps(requiredType);
        return (Map<String, T>) map;
    }

    /**
     * 找到符合相应类型的服务map
     *
     * @param requiredType 服务类型
     * @param <T>          服务类型
     * @return 第一个找到的map
     */
    private <T> Map<String, Object> digMaps(Class<T> requiredType) {
        Map<String, Object> all = new HashMap<String, Object>();
        Set<Class> keys = services.keySet();
        for (Class serviceClass : keys) {
            if (requiredType.isAssignableFrom(serviceClass)) {
                //如果出现子类型中的服务与已有存在的冲突了怎么办？
                Map<String, Object> map = services.get(serviceClass);
                for (String key : map.keySet()) {
                    Object service = map.get(key);
                    Object exist = all.get(key);
                    if (exist != null) {
                        int n = 1;
                        String newKey;
                        do {
                            newKey = key + "#" + n++;
                        } while (all.containsKey(newKey));
                        all.put(newKey, service);
                    } else {
                        all.put(key, service);
                    }
                }
            }
        }
        return all;
    }
}


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

import net.happyonroad.component.classworld.MainClassLoader;
import net.happyonroad.component.container.support.DefaultLaunchEnvironment;
import net.happyonroad.component.container.support.ShutdownHook;
import net.happyonroad.component.core.Component;
import net.happyonroad.component.core.ComponentException;
import net.happyonroad.component.core.support.ComponentURLStreamHandlerFactory;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.remoting.rmi.RmiServiceExporter;
import org.springframework.util.StringUtils;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;

import static net.happyonroad.util.LogUtils.banner;
import static org.apache.commons.lang.time.DurationFormatUtils.formatDurationHMS;

/**
 *
扩展了Maven Plexus的Launcher

 *

 *
简化uberjar规范

 *
解析被启动的目标组件的Dependencies信息为Classworld

 *
构建合适的Spring Context

 *
与其他组件交互

 *

 *
对某个组件进行
 *

加载
启动
停止
卸载

 *

 */
public class AppLauncher implements Executable {
    private static Logger logger = LoggerFactory.getLogger(AppLauncher.class.getName());
    private   long              systemStartAt;
    protected LaunchEnvironment environment;
    protected Component         mainComponent;
    //protected PomClassWorld     pomWorld;

    public AppLauncher(Component mainComponent, LaunchEnvironment environment) {
        super();
        if (mainComponent == null) {
            throw new IllegalArgumentException("The main component must been specified!");
        }
        this.systemStartAt = System.currentTimeMillis();
        this.mainComponent = mainComponent;
        //String theMainClassName = this.mainComponent.getManifestAttribute("Main-Class");
        //setAppMain(theMainClassName, this.mainComponent.getId());
        this.environment = environment;
        //super.world = this.pomWorld = new PomClassWorld();
        //this.pomWorld.setMainComponentId(this.mainComponent.getId());
        System.setProperty("file.encoding", "UTF-8");
    }

    /**
     * 启动当前的组件Jar包
     */
    public void start() throws Exception {
        try {
            long start = System.currentTimeMillis();
            //Ensure there is no legacy signal files
            FileUtils.deleteQuietly(getSignal("exit"));
            FileUtils.deleteQuietly(getSignal("exiting"));
            logger.info(banner("Loading components starts from {}", this.mainComponent));
            environment.load(this.mainComponent);
            logger.info(banner("Loaded  components starts from {}", this.mainComponent));
            if (!StringUtils.isEmpty(System.getProperty("app.port")))
                exportAsRMI();

            addShutdownHook();
            logger.info(banner("The {} is started", getAppName()));
            logger.info(banner("System starts took {}", formatDurationHMS(System.currentTimeMillis() - start)));
            //Thread.currentThread().setDaemon(true);
            //让主线程基于STDIO接受交互命令
            //以后应该让CLI组件托管这块工作
            //if (!StringUtils.isEmpty(System.getProperty("app.port")))
            //    processCommands();
            if (StringUtils.isEmpty(System.getProperty("app.port")))
                checkSignalPeriodically();
        } catch (Exception ex) {
            logger.error("Failed to start", ex);
            System.exit(1);
        }
    }

    public static int readSystemAppIndex() {
        return Integer.valueOf(System.getProperty("app.index", "0"));
    }

    public static File getSignal(String name) {
        int appIndex = readSystemAppIndex();
        if (appIndex == 0) {
            return new File("tmp/" + name);
        } else {
            return new File("tmp/" + name + "." + appIndex);
        }
    }

    private void checkSignalPeriodically() {
        while (!exiting()) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                //skip
            }
            File signal = getSignal("exit");
            if( !signal.exists() ) continue;

            if (signal.lastModified() < systemStartAt) {
                // a legacy file
                FileUtils.deleteQuietly(signal);
            } else {
                FileUtils.deleteQuietly(signal);
                exit();
            }
        }

    }

    /**
     * 退出
     */
    public void exit() {
        if (exiting()) {
            logger.debug("Another thread is shutting down");
            return;
        }
        final long start = System.currentTimeMillis();
        //noinspection finally
        File exitingSignal = getSignal("exiting");
        try {
            logger.info(banner("Unloading the main component {}", this.mainComponent));
            FileUtils.touch(exitingSignal);
            environment.unload(this.mainComponent);
            logger.info(banner("Unloaded  the main component {}", this.mainComponent));
        } catch (Exception ex) {
            logger.error(ex.getMessage(), ex);
        } finally {
            FileUtils.deleteQuietly(exitingSignal);
            //启动一个额外的线程停止自身
            new Thread("Stopper") {
                @Override
                public void run() {
                    logger.info(banner("System stops took {}", formatDurationHMS(System.currentTimeMillis() - start)));
                    System.exit(0);
                }
            }.start();
        }
    }

    @Override
    public boolean exiting() {
        return getSignal("exiting").exists();
    }

    /*重新加载*/
    public void reload() {

    }

    // ------------------------------------------------------------
    //     辅助管理
    // ------------------------------------------------------------

    /**
     * 将本对象通过Spring的RMI机制暴露出去
     *
     * @throws Exception
     */
    private void exportAsRMI() throws Exception {
        String appHost = System.getProperty("app.host");
        String appPort = System.getProperty("app.port");
        RmiServiceExporter exporter = new RmiServiceExporter();
        exporter.setServiceInterface(Executable.class);
        String serviceName = getAppName() + "Launcher";
        //exporter.setRegistryHost(appHost);
        exporter.setRegistryPort(Integer.valueOf(appPort));
        exporter.setServiceName(serviceName);
        exporter.setService(this);
        exporter.afterPropertiesSet();

        String serviceUrl = String.format("rmi://%s:%s/%s", appHost, appPort, serviceName);
        logger.info(banner("Export Executable Service at {}", serviceUrl));
    }

    private void addShutdownHook() {
        Runtime.getRuntime().addShutdownHook(new ShutdownHook(this));
    }

    protected String getAppName() {
        return System.getProperty("app.name", "Unknown");
    }

    /**
     * Process user specified command
     *
     * @param command the command
     */
    protected void process(String command) {
        try {
            Method method = this.getClass().getMethod(command);
            logger.info("Try to delegate '{}' to launcher directly.", command);
            method.invoke(this);
            logger.info("Invoke '{}' to launcher directly successfully. \r\n", command);
        } catch (NoSuchMethodException e) {
            logger.warn("unrecognized command: '{}'", command);
        } catch (Exception e) {
            logger.warn("Failed to execute: '{}'", command);
        }
    }

    // ------------------------------------------------------------
    //     入口静态方法
    // ------------------------------------------------------------

    /**
     *
支持系统启动与停止

     *

     *
启动方式

     * 开发模式下，通过命令行以：
     *
java -classpath jdk/jars;container/classes;other/classes
     * dnt.component.container.AppLauncher depended.group.artifact-version

     * 生产模式下，通过命令行以：
     *
java -Dapp.home=path/to/home -jar path/to/container.jar depended.group.artifact-version

     * 的方式启动
     *

     * 定制方式：
     * 用户可以通过　-Dapp.launch.environment=the.launch.env.class 的方式定制启动逻辑
     * 但请注意将相应的Launch Env类配置在主jar的class-path中（或开发环境的pom依赖下）
     *

     * 退出情况:
     *

     *
0: 正常退出

     *
1: Launch Env不能初始化

     *
2: 目标主jar|classes未定义

     *
3: 目标主jar|classes无法初始化

     *
100: 未知异常

     *
其他：业务异常，待明确

     *

     *

     *
停止方式

     * 开发模式下，通过命令行以：
     *
java -classpath jdk/jars;container/classes;other/classes
     * dnt.component.container.AppLauncher depended.group.artifact-version --stop

     * 生产模式下，通过命令行以：
     *
java -Dapp.home=path/to/home -jar path/to/container.jar depended.group.artifact-version --stop

     * 的方式停止
     *
     * @param args 最少要指定一个入口jar文件或者入口主模块输出路径
     */
    public static void main(String[] args) {
        if (args.length < 1)
            throw new IllegalArgumentException("You must specify a main dependency");
        //设定的路径可能包括lib/等目录
        String[] strings = args[0].split("\\|/");
        if (strings.length > 1) {
            args[0] = strings[strings.length - 1];
        }
        try {
            // When started by Service wrapper, the context class loader is URLClassLoader of wrapper.jar
            //  but this main class is loaded by the framework jar's ClassLoader(FactoryClassLoader)
            if (AppLauncher.class.getClassLoader() != ClassLoader.getSystemClassLoader()) {
                //说明是基于Service Wrapper启动
                Thread.currentThread()
                      .setContextClassLoader(MainClassLoader.getInstance(AppLauncher.class.getClassLoader()));
            } else {
                Thread.currentThread().setContextClassLoader(MainClassLoader.getInstance());
            }
            // To register the url handler by current context class loader, instead of system bootstrap class loader
            URL.setURLStreamHandlerFactory(ComponentURLStreamHandlerFactory.getFactory());
            int exitCode = mainWithExitCode(args);
            System.exit(exitCode);
        } catch (LaunchException e) {
            System.err.println(describeException(e));
            System.exit(e.getExitCode());
        } catch (Exception e) {
            System.err.println(describeException(e));
            System.exit(100);
        }
    }

    static String[] appPatterns;

    static {
        appPatterns = System.getProperty("app.prefix", "dnt.;net.happyonroad").split(";");
    }

    public static String describeException(Throwable ex) {
        StringBuilder message = new StringBuilder();
        message.append(ExceptionUtils.getRootCauseMessage(ex)).append("\n");
        String[] traces = ExceptionUtils.getRootCauseStackTrace(ex);
        for (int i = 1; i < traces.length; i++) {
            String trace = traces[i];
            message.append(trace).append("\n");
            if (isApplicationTrace(trace)) {
                break;
            }
        }
        return message.toString();
    }

    static boolean isApplicationTrace(String trace) {
        if (appPatterns.length == 0) return true;
        for (String pattern : appPatterns) {
            int i = trace.indexOf(pattern);
            if (i > 0) {
                char c = trace.charAt(i - 1);
                if (Character.isSpaceChar(c)) {
                    return true;
                }
            } else if (i == 0) {
                return true;
            }
        }
        return false;
    }

    /**
     * 启动逻辑，暂时还没有支持停止
     *
     * @param args The application command-line arguments.
     * @return an integer exit code
     * @throws Exception If an error occurs.
     */
    public static int mainWithExitCode(String[] args) throws Exception {
        LaunchEnvironment environment = detectEnvironment();

        Component mainComponent = null;
        try {
            logger.debug(banner("Resolving starts from {}", args[0]));
            mainComponent = environment.resolveComponent(args[0]);
            logger.debug(banner("Resolved  starts from {}", args[0]));

            AppLauncher launcher = environment.createLauncher(mainComponent);
            //去掉主入口参数之后，将其余参数传入
            String[] newArgs = new String[args.length - 1];
            System.arraycopy(args, 1, newArgs, 0, args.length - 1);
            //启动程序，根据剩余的 newArgs，可能有多种启动方式
            // 包括: 打印组件依赖列表
            //      启动系统(在当前进程内启动系统)
            //      停止系统(通过本机Socket停止另外一个已经在运行的系统)
            environment.execute(launcher, newArgs);
            return 0;
        } catch (ComponentException ex) {
            logger.error("{} : {}", ex.getPath(), describeException(ex));
            return -1;
        } catch (Throwable ex) {
            logger.error("Failed: {}", describeException(ex));
            return -1;
        } finally {
            if (mainComponent != null) {
                environment.unload(mainComponent);
            }
            environment.shutdown();
        }
    }

    protected static LaunchEnvironment detectEnvironment() throws Exception {
        String env = System.getProperty("app.launch.environment");
        if (env != null) {
            return (LaunchEnvironment) Class.forName(env).newInstance();
        } else {
            return new DefaultLaunchEnvironment();
        }

    }
}


/**
 * Developer: Kadvin Date: 15/2/3 上午9:54
 */
package net.happyonroad.spring.config;

import net.happyonroad.spring.service.LocalServiceExporter;
import net.happyonroad.spring.service.ServiceExporter;
import net.happyonroad.spring.support.DefaultLocalServiceExporter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.EnableMBeanExport;
import org.springframework.jmx.support.RegistrationPolicy;

/**
 *
抽象的Application Configuration

 * 主要功能是：
 *

 *

 *
注入 Application Context

 *
注入 Service Export

 *
提供简便的 exports 方法

 *
构造 Local Service Exporter

 *
提供简便的 本地 exports 方法（无需 bean reference)

 *

 */
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
public abstract class AbstractAppConfig extends AbstractUserConfig implements InitializingBean {
    @Autowired
    protected ServiceExporter exporter;
    @Autowired
    protected ApplicationContext applicationContext;

    @Bean
    LocalServiceExporter localExporter() {
        return new DefaultLocalServiceExporter(exporter, applicationContext);
    }

    // 简单封装 exporter

    /**
     *
暴露某个服务

     *
     * @param serviceClass 需要暴露的服务接口
     * @param service      实际的服务实例（不能为空)
     * @param hint         服务的注释
     * @param <T>          服务的类型
     */
    protected <T> void exports(Class<T> serviceClass, T service, String hint) {
        exporter.exports(serviceClass, service, hint);
    }

    /**
     *
暴露某个服务(hint = default)

     *
     * @param serviceClass 需要暴露的服务接口
     * @param service      实际的服务实例（不能为空)
     * @param <T>          服务的类型
     */
    protected <T> void exports(Class<T> serviceClass, T service) {
        exporter.exports(serviceClass, service);
    }

    // 封装 local exporter

    /**
     *
暴露某个服务

     *
     * @param serviceClass 需要暴露的服务接口
     * @param hint         服务的注释
     * @param <T>          服务的类型
     */
    protected <T> void exports(Class<T> serviceClass, String hint) {
        localExporter().exports(serviceClass, hint);
    }

    /**
     *
暴露某个服务(hint = default)

     *
     * @param serviceClass 需要暴露的服务接口
     * @param <T>          服务的类型
     */
    protected <T> void exports(Class<T> serviceClass) {
        localExporter().exports(serviceClass);
    }

    protected <T> T getBean(Class<T> serviceClass){
        return localExporter().getBean(serviceClass);
    }

    @Override
    public final void afterPropertiesSet() throws Exception {
        beforeExports();
        try {
            doExports();
        } finally {
            afterExports();
        }
    }

    /**
     * 执行服务导出的之前工作
     */
    protected void beforeExports() {}

    /**
     * 执行服务导出的工作
     */
    protected void doExports() {}

    /**
     * 执行服务导出的之后工作
     */
    protected void afterExports() {}
}


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

import net.happyonroad.component.container.RepositoryScanner;
import net.happyonroad.component.core.Component;
import net.happyonroad.component.core.ComponentResource;
import net.happyonroad.component.core.Versionize;
import net.happyonroad.component.core.exception.InvalidComponentException;
import net.happyonroad.component.core.exception.InvalidComponentNameException;
import net.happyonroad.spring.support.ComponentResourcePatternResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.naming.SelfNaming;

import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.jar.Attributes;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
组件对象

 *

 * 从pom.xml中解析出来，包括Group、Artifact、Version、Dependencies等信息
 * 这个类其实是对Maven Artifact的一种简化
 */
@SuppressWarnings("unused")
@ManagedResource(description = "Spring Component")
public class DefaultComponent implements Component, SelfNaming {
    private static final String CLASSPATH_THIS_URL_PREFIX = "classpath*:";
    private static       Logger           logger                    =
            LoggerFactory.getLogger(DefaultComponent.class.getName());
    private static final Pattern          INTERPOLATE_PTN           = Pattern.compile("\\$\\{([^}]+)\\}");
    private static final String[]         ATTRIBUTE_NAMES           =
            new String[]{"groupId", "artifactId", "version", "type",
                         "classifier", "release", "name",
                         "description",
                         "url"};

    static {
        Arrays.sort(ATTRIBUTE_NAMES);
    }

    private Component parent;

    private String groupId;

    private String artifactId;

    private String type;//pom or jar, war, rar, and so on

    private String classifier;//snapshot, release, javadoc, source, java4, java5, java6, or other feature...

    private Resource             underlyingResource;//对应的实际文件
    private ComponentResource    resource;//对应的资源文件
    //DependencyManagement声明的是所有可能的依赖，但解析过程中，不应该触发它们被解析
    private DependencyManagement dependencyManagement;
    //这里声明的实际的依赖，可以引用或参考/继承 dependencyManagement 声明的依赖
    private List<Dependency>     dependencies;//依赖信息解析成为一堆字符串
    private List<Component>      dependedComponents;//依赖信息解析成为一堆字符串
    private List<String>         moduleNames;       //子模块，子项目，子组件
    private List<Component>      modules;       //子模块，子项目，子组件

    private String version;//核心版本，如: 1.0.0

    private boolean release = true;

    private String name, description, url;

    private Properties              properties;
    private ClassLoader             classLoader;
    private ApplicationContext      application;
    private Map<String, String>     defaults;
    private List<RepositoryScanner> scanners;
    private ResourcePatternResolver resourceLoader;
    private Attributes mainAttributes = null ;

    // XStream Reflection 时并不需要提供一个缺省构造函数

    /**
     * 根据依赖里面定义的信息构建一个组件实例
     *
     * @param from 指定的组件信息，并不是指构建一个带有这个依赖的组件
     */
    public DefaultComponent(Versionize from) {
        this(from.getGroupId(), from.getArtifactId(), from.getVersion(), from.getClassifier(), from.getType());
    }

    public DefaultComponent(String groupId, String artifactId, String version, String classifier, String type) {
        this.groupId = groupId;

        this.artifactId = artifactId;

        this.version = version;

        this.classifier = classifier;

        this.type = type;

        validateIdentity();

        this.properties = new Properties();
    }

    private void validateIdentity() {
        if (empty(groupId)) {
            throw new InvalidComponentException(groupId, artifactId, getVersion(), type,
                                                "The groupId cannot be empty.");
        }

        if (artifactId == null) {
            throw new InvalidComponentException(groupId, "null", getVersion(), type,
                                                "The artifactId cannot be empty.");
        }

        if (type == null) {
            throw new InvalidComponentException(groupId, artifactId, getVersion(), "null", "The type cannot be empty.");
        }

        if (version == null) {
            throw new InvalidComponentException(groupId, artifactId, getVersion(), type,
                                                "The version cannot be empty.");
        }
    }

    private boolean empty(String value) {
        return (value == null) || (value.trim().length() < 1);
    }

    @ManagedAttribute
    @Override
    public String getGroupId() {
        return groupId;
    }

    @ManagedAttribute
    @Override
    public String getArtifactId() {
        return artifactId;
    }

    @ManagedAttribute
    @Override
    public String getVersion() {
        return version;
    }

    @ManagedAttribute
    @Override
    public String getClassifier() {
        return classifier;
    }

    public boolean hasClassifier() {
        return classifier != null && !"".equalsIgnoreCase(classifier.trim());
    }

    @ManagedAttribute
    @Override
    public String getType() {
        return type;
    }

    @ManagedAttribute
    public String getName() {
        return name;
    }

    @ManagedAttribute
    @Override
    public String getDisplayName() {
        return name == null ? getId() : name;
    }

    @ManagedAttribute
    public String getDescription() {
        return description;
    }

    @ManagedAttribute
    public String getUrl() {
        return url;
    }

    public void setVersion(String version) {
        this.version = version;
        this.splitVersionAndClassifier();
    }

    public void setClassifier(String classifier) {
        this.classifier = classifier;
        //Classifier设置之后，就要自动设置release属性
        if (isSnapshot()) setRelease(false);
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public void setType(String type) {
        this.type = type;
    }

    public void setUnderlyingResource(Resource underlyingResource) {
        this.underlyingResource = underlyingResource;
    }

    public void setResource(ComponentResource resource) {
        this.resource = resource;
        if( resource != null )
            this.mainAttributes = resource.getManifest().getMainAttributes();
        this.resourceLoader = new ComponentResourcePatternResolver(this);
    }

    @ManagedAttribute
    @Override
    public Resource getUnderlyingResource() {
        return underlyingResource;
    }

    @Override
    public URL getURL() {
        String fileName = null;
        try {
            if (resource == null) {
                String originName = underlyingResource.getFilename();
                if (originName == null) originName = underlyingResource.getDescription();
                fileName = originName.replace(".pom", ".jar");
            } else {
                fileName = resource.getFileName();
            }
            return new URL("component:" + fileName);
        } catch (MalformedURLException e) {
            throw new IllegalStateException("Can't construct the component url: " + fileName, e);
        }
    }

    @Override
    public Set<URL> getLibURLs() {
        Set<URL> urls = new HashSet<URL>();
        String[] strings = resource == null ? new String[0] : resource.getDependencies();
        for (String line : strings) {
            try {
                if (isApplication(line)) continue;
                urls.add(new URL("component:" + line));
            } catch (Exception e) {
                throw new IllegalStateException("The " + this + " META-INF/INDEX.DEPENDS: " + line + " is invalid");
            }
        }
        return urls;
    }

    @Override
    public List<String> getAppBriefIds() {
        List<String> briefIds = new ArrayList<String>();
        String[] strings = resource == null ? new String[0] : resource.getDependencies();
        for (String line : strings) {
            if (isApplication(line)) briefIds.add(line);
        }
        return briefIds;
    }

    public ComponentResource getResource() {
        return resource;
    }
    // ----------------------------------------------------------------------
    //
    // ----------------------------------------------------------------------

    @ManagedAttribute
    @Override
    public String getId() {
        return getDependencyConflictId();
    }

    @Override
    public ObjectName getObjectName() {
        String name = getBriefId();
        try {
            return new ObjectName("spring.components:name=" + name);
        } catch (MalformedObjectNameException e) {
            throw new IllegalArgumentException("Can't create component object name:" + name);
        }
    }

// ----------------------------------------------------------------------
    // Object overrides
    // ----------------------------------------------------------------------

    public String toString() {
        StringBuffer sb = new StringBuffer();
        if (getGroupId() != null) {
            sb.append(getGroupId());
            sb.append("/");
        }
        appendArtifactTypeClassifierString(sb);
        return sb.toString();
    }

    public String getDependencyConflictId() {
        StringBuffer sb = new StringBuffer();
        sb.append(getGroupId());
        sb.append("/");
        appendArtifactTypeClassifierString(sb);
        return sb.toString();
    }

    private void appendArtifactTypeClassifierString(StringBuffer sb) {
        sb.append(getArtifactId());
        sb.append("@").append(getVersion() == null ? "<*>" : getVersion());
        if (hasClassifier()) {
            sb.append("-");
            sb.append(getClassifier());
        }
        sb.append(".");
        sb.append(getType() == null ? "<*>" : getType());
    }

    public int hashCode() {
        int result = 17;
        result = 37 * result + groupId.hashCode();
        result = 37 * result + artifactId.hashCode();
        result = 37 * result + (type == null ? "<*>".hashCode() : type.hashCode());
        if (version != null) {
            result = 37 * result + version.hashCode();
        }
        result = 37 * result + (classifier != null ? classifier.hashCode() : 0);
        return result;
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        }

        if (!(o instanceof Component)) {
            return false;
        }

        Component a = (Component) o;

        if (!a.getGroupId().equals(groupId)) {
            return false;
        } else if (!a.getArtifactId().equals(artifactId)) {
            return false;
        } else if (!a.getVersion().equals(version)) {
            return false;
        } else if (!a.getType().equals(type)) {
            return false;
        } else if (a.getClassifier() == null ? classifier != null : !a.getClassifier().equals(classifier)) {
            return false;
        }

        // We don't consider the version range in the comparison, just the resolved version

        return true;
    }

    public int compareTo(@SuppressWarnings("NullableProblems") Component a) {
        int result = groupId.compareTo(a.getGroupId());
        if (result == 0) {
            result = artifactId.compareTo(a.getArtifactId());
            if (result == 0) {
                result = type.compareTo(a.getType());
                if (result == 0) {
                    if (classifier == null) {
                        if (a.getClassifier() != null) {
                            result = 1;
                        }
                    } else {
                        if (a.getClassifier() != null) {
                            result = classifier.compareTo(a.getClassifier());
                        } else {
                            result = -1;
                        }
                    }
                    if (result == 0) {
                        // We don't consider the version range in the comparison, just the resolved version
                        result = version.compareTo(a.getVersion());
                    }
                }
            }
        }
        return result;
    }

    public Component getParent() {
        return parent;
    }

    public void setParent(Component parent) {
        this.parent = parent;
    }

    public List<Dependency> getDependencies() {
        if (dependencies == null) dependencies = new ArrayList<Dependency>(0);
        return dependencies;
    }

    @Override
    public Dependency getDependency(Dependency dependency) {
        if (dependencies == null)
            return null;
        for (Dependency candidate : dependencies) {
            if (dependency.conflict(candidate))
                return candidate;
        }
        return null;
    }

    public DependencyManagement getDependencyManagement() {
        if (dependencyManagement == null)
            dependencyManagement = new DependencyManagement();
        return dependencyManagement;
    }

    public List<Component> getDependedComponents() {
        if (dependedComponents == null) dependedComponents = new ArrayList<Component>(0);
        return dependedComponents;
    }

    @Override
    public Set<Dependency> getAllDependencies() {
        Set<Dependency> all = new HashSet<Dependency>();
        if (getDependencies() != null) {
            all.addAll(getDependencies());
        }
        if (this.parent != null) {
            all.addAll(this.parent.getAllDependencies());
        }
        return all;
    }

    public Set<Component> getAllDependedComponents() {
        Set<Component> all = new HashSet<Component>();
        if (dependedComponents != null) {
            for (Component depended : dependedComponents) {
                all.add(depended);
                all.addAll(depended.getAllDependedComponents());
            }
        }
        if (this.parent != null) {
            all.addAll(this.parent.getAllDependedComponents());
        }
        return all;
    }

    public void setDependedComponents(List<Component> dependedComponents) {
        this.dependedComponents = dependedComponents;
    }

    @ManagedAttribute
    public List<String> getModuleNames() {
        return moduleNames;
    }

    public List<Component> getModules() {
        return modules;
    }

    public void setDependencies(List<Dependency> dependencies) {
        this.dependencies = dependencies;
    }

    public void setModuleNames(List<String> modules) {
        this.moduleNames = modules;
    }

    public void setModules(List<Component> modules) {
        this.modules = modules;
    }

    public void setGroupId(String groupId) {
        this.groupId = groupId;
    }

    public void setArtifactId(String artifactId) {
        this.artifactId = artifactId;
    }

    @ManagedAttribute
    @Override
    public boolean isSnapshot() {
        return SNAPSHOT_VERSION.equalsIgnoreCase(getClassifier());
    }

    public void setResolvedVersion(String version) {
        this.version = version;
        // retain baseVersion
    }

    public void setRelease(boolean release) {
        this.release = release;
    }

    @ManagedAttribute
    @Override
    public boolean isRelease() {
        return release;
    }

    @ManagedAttribute
    @Override
    public boolean isAggregating() {
        return "pom".equals(getType());
    }

    @ManagedAttribute
    public Properties getProperties() {
        return properties;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    //其实如果以当前project为this，把env, system作为上下文， 通过对象图的方式提供变量索引，实现起来更好
    @Override
    public String resolveVariable(String key) {
        if (key.startsWith("project.")) {
            return resolveVariable(key.substring("project.".length()));
        } else if (key.startsWith("pom.")) {
            return resolveVariable(key.substring("pom.".length()));
        } else if (key.startsWith("parent.")) {
            if (parent == null)
                return null;
            return parent.resolveVariable(key.substring("parent.".length()));
        } else if (key.startsWith("env.")) {
            return System.getenv(key.substring("env.".length()));
        }
        if ("packaging".equals(key))
            key = "type";
        if (Arrays.binarySearch(ATTRIBUTE_NAMES, key) >= 0) {
            try {
                Field field = getClass().getDeclaredField(key);
                return String.valueOf(field.get(this));
            } catch (Exception e) {
                return null;
            }
        } else if (System.getProperty(key) != null) {
            return System.getProperty(key);
        } else if (properties != null) {
            String property = properties.getProperty(key);
            if (property == null && parent != null) {
                property = parent.resolveVariable(key);
            }
            return property;
        } else {
            // delegate parent to resolve it
            if (parent == null) return null;
            return parent.resolveVariable(key);
        }
    }

    @ManagedAttribute
    @Override
    public boolean isPlain() {
        return !isApplication() || getResource() == null;
    }

    @Override
    public String getManifestAttribute(String attributeName) {
        String value = null;
        if (this.mainAttributes != null) {
            value = mainAttributes.getValue(attributeName);
        }
        if (value == null && defaults != null) {
            value = defaults.get(attributeName);
        }
        return value;
    }

    @Override
    public void setManifestAttribute(String key, String value) {
        if (this.defaults == null) this.defaults = new HashMap<String, String>();
        this.defaults.put(key, value);
    }

    public void validate() throws InvalidComponentNameException {
        if (groupId == null)
            throw new InvalidComponentNameException("The component group id can't be null");
        if (artifactId == null)
            throw new InvalidComponentNameException("The component artifact id can't be null");
        if (type == null)
            throw new InvalidComponentNameException("The component type can't be null");
    }

    public void splitVersionAndClassifier() {
        if (version.contains("-")) {
            String[] vac = this.version.split("-");
            if (!vac[1].contains("SNAPSHOT")) {
                this.version = vac[0];
                this.classifier = vac[1];
                return;
            }
        }
        String[] versionAndClassifier = Dependency.splitClassifierFromVersion(version);
        this.version = versionAndClassifier[0];
        if (versionAndClassifier[1].length() > 0) setClassifier(versionAndClassifier[1]);
    }

    public void interpolate() {
        if (groupId != null)
            this.groupId = interpolate(groupId);
        if (artifactId != null)
            this.artifactId = interpolate(artifactId);
        //暂时主要处理一些启动相关的关键属性，如version
        if (version != null)
            this.setVersion(interpolate(version));
        if (name != null)
            this.name = interpolate(name);
        if (classifier != null)
            this.classifier = interpolate(classifier);
        if (description != null)
            this.description = interpolate(description);
        if (dependencies != null) {
            for (Dependency dependency : dependencies) {
                dependency.interpolate(this);
            }
        }
        if (dependencyManagement != null) {
            for (Dependency dependency : dependencyManagement.getDependencies()) {
                dependency.interpolate(this);
            }
        }
    }

    public String interpolate(String origin) {
        Matcher m = INTERPOLATE_PTN.matcher(origin);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            String variable = m.group(1);
            String replacement = resolveVariable(variable);
            if (replacement == null) {
                if (isApplication(groupId))
                    throw new InvalidComponentException(this.groupId, this.artifactId, this.version, this.type,
                                                        "Can't interpolate variable [" + variable + "]");
                else {
                    logger.warn("Can't interpolate variable ${" + variable + "} for " + this);
                    continue;
                }
            }
            //解析出来的变量可能还需要再解析
            if (INTERPOLATE_PTN.matcher(replacement).find()) {
                replacement = interpolate(replacement);
            }
            try {
                m.appendReplacement(sb, replacement);
            } catch (IllegalArgumentException e) {
                e.printStackTrace();//just for catch it to debug
                throw e;
            }
        }
        m.appendTail(sb);
        return sb.toString().trim();
    }

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

    static public boolean isApplication(String briefId) {
        if (briefId.startsWith("net.happyonroad")) return true;
        //这个属性可以通过设置 app.prefix系统属性进行干预，支持多个prefix用分号分隔
        //当符合该条件的模块没有被解析出来时，系统将会停止解析，抛出异常；
        //而不符合该条件的模块，往往是第三方未用到的模块，解析失败不应该停止
        String prefixes = System.getProperty("app.prefix", "dnt.");
        String[] strings = prefixes.split(";");
        for (String prefix : strings) {
            if (briefId.startsWith(prefix)) return true;
        }
        return false;
    }

    public ClassLoader getClassLoader() {
        return classLoader;
    }

    public void setClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    public void perform(Runnable job) throws Exception {
        ClassLoader originCl = Thread.currentThread().getContextClassLoader();
        try {
            ClassLoader classLoader = this.getClassLoader();
            Thread.currentThread().setContextClassLoader(classLoader);
            job.run();
        } finally {
            Thread.currentThread().setContextClassLoader(originCl);
        }
    }

    public <T> T perform(Callable<T> job) throws Exception {
        ClassLoader originCl = Thread.currentThread().getContextClassLoader();
        try {
            ClassLoader classLoader = this.getClassLoader();
            Thread.currentThread().setContextClassLoader(classLoader);
            return job.call();
        } finally {
            Thread.currentThread().setContextClassLoader(originCl);
        }
    }

    @Override
    public boolean dependsOn(Component another) {
        Set<Component> depends = getAllDependedComponents();
        for (Component depended : depends) {
            if (depended.equals(another))
                return true;
        }
        return false;
    }

    @Override
    public String getBriefId() {
        return getGroupId() + "." + getArtifactId() + "-" + getVersion();
    }

    public ApplicationContext getApplication() {
        return application;
    }

    public void setApplication(ApplicationContext application) {
        this.application = application;
    }

    @Override
    public void registerScanner(RepositoryScanner scanner) {
        if (scanners == null) {
            scanners = new LinkedList<RepositoryScanner>();
        }
        scanners.add(scanner);
    }

    @Override
    public List<RepositoryScanner> getScanners() {
        return scanners;
    }

    @Override
    public ResourcePatternResolver getResourceLoader() {
        return resourceLoader;
    }
}


/**
 * Developer: Kadvin Date: 14-6-10 上午9:35
 */
package net.happyonroad.spring.context;

import net.happyonroad.spring.support.SmartApplicationEventMulticaster;
import org.apache.commons.lang.reflect.FieldUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyResourceConfigurer;
import org.springframework.context.*;
import org.springframework.context.event.AbstractApplicationEventMulticaster;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;

import java.lang.reflect.Method;
import java.util.*;

import static org.springframework.context.support.AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME;

/**
 * 将各个context无法DRY的功能集中到这里
 */
public class ContextUtils {
    private final static Map<String, Set<ApplicationEvent>> events      = new HashMap<String, Set<ApplicationEvent>>();

    /**
     * 继承上级上下文中的属性配置器
     *
     * @param inheriting  上级上下文
     * @param context 当前上下文
     */
    public static void inheritParentProperties(ApplicationContext inheriting,
                                               GenericApplicationContext context) {
        if (!(inheriting instanceof AbstractApplicationContext)) return;
        List<BeanFactoryPostProcessor> processors =
                ((AbstractApplicationContext) inheriting).getBeanFactoryPostProcessors();
        for (BeanFactoryPostProcessor processor : processors) {
            if (processor instanceof PropertyResourceConfigurer)
                context.addBeanFactoryPostProcessor(processor);
        }
    }

    /**
     * 修改特定上下文的Event Multicaster的默认实现
     *
     * @param context 被修改的上下文
     */
    public static void initApplicationEventMulticaster(GenericApplicationContext context) {
        ApplicationEventMulticaster multicaster;
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
            multicaster =
                    beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
        } else {
            multicaster = new SmartApplicationEventMulticaster(beanFactory);
            beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, multicaster);
        }
        setApplicationEventMulticaster(context, multicaster);

    }

    private static void setApplicationEventMulticaster(GenericApplicationContext context,
                                                         ApplicationEventMulticaster applicationEventMulticaster) {
        try {
            FieldUtils.writeField(context, "applicationEventMulticaster", applicationEventMulticaster, true);
        } catch (IllegalAccessException e) {
            throw new ApplicationContextException("Can't hacking the spring context for applicationEventMulticaster", e);
        }
    }

    public static void publishEvent(List<ApplicationContext> contexts, ApplicationEvent event){
        filterContexts(contexts, event);
        registerEvents(contexts, event);
        try {
            innerPublish(contexts, event);
        } finally {
            cleanEvents(contexts, event);
        }
    }

    private static void registerEvents(List<ApplicationContext> contexts, ApplicationEvent event) {
        for (ApplicationContext context : contexts) {
            Set<ApplicationEvent> set = events.get(context.getId());
            if( set == null ) {
                set = new HashSet<ApplicationEvent>();
                events.put(context.getId(), set);
            }
            set.add(event);
        }
    }

    private static void filterContexts(List<ApplicationContext> contexts, ApplicationEvent event) {
        Iterator<ApplicationContext> it = contexts.iterator();
        while (it.hasNext()) {
            ApplicationContext context = it.next();
            Set<ApplicationEvent> set = events.get(context.getId());
            if( set != null && set.contains(event)) it.remove();
        }
    }

    private static void cleanEvents(List<ApplicationContext> contexts, ApplicationEvent event) {
        for (ApplicationContext context : contexts) {
            Set<ApplicationEvent> set = events.get(context.getId());
            if( set != null) set.remove(event);
        }
    }

    protected static void innerPublish(List<ApplicationContext> contexts, ApplicationEvent event) {
        Logger eventLogger = getEventLogger(event);
        //向所有的context发布，context里面有防止重复的机制
        // 2. 提速
        Set<ApplicationListener<ApplicationEvent>> listeners = new HashSet<ApplicationListener<ApplicationEvent>>();
        for (ApplicationContext context : contexts) {
            if( context != null ) {
                if( context instanceof ConfigurableApplicationContext){
                    if( !((ConfigurableApplicationContext) context).isActive() ) continue;
                }
                ApplicationEventMulticaster multicaster = (ApplicationEventMulticaster) context.getBean(
                        AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME);
                Collection<ApplicationListener<?>> partListeners;
                if( multicaster instanceof SmartApplicationEventMulticaster){
                    SmartApplicationEventMulticaster smartOne = (SmartApplicationEventMulticaster) multicaster;
                    partListeners = smartOne.getApplicationListeners(event);
                } else {
                    try {
                        Class<AbstractApplicationEventMulticaster> klass = AbstractApplicationEventMulticaster.class;
                        Method method = klass.getDeclaredMethod("getApplicationListeners", ApplicationEvent.class);
                        method.setAccessible(true);
                        //noinspection unchecked
                        partListeners = (Collection<ApplicationListener<?>>) method.invoke(multicaster, event);
                    } catch (Exception e) {
                        eventLogger.warn("Can't steal event listener from " + multicaster);
                        partListeners = Collections.emptyList();
                    }
                }
                for (ApplicationListener<?> listener : partListeners) {
                    //noinspection unchecked
                    listeners.add((ApplicationListener<ApplicationEvent>) listener);
                }
            }
        }
        if( !listeners.isEmpty() ){
            LinkedList<ApplicationListener<ApplicationEvent>> list = new LinkedList<ApplicationListener<ApplicationEvent>>();
            //noinspection unchecked
            list.addAll(listeners);
            OrderComparator.sort(list);
            if( eventLogger.isInfoEnabled() ){
                StringBuilder sb = new StringBuilder();
                Iterator<ApplicationListener<ApplicationEvent>> it = list.iterator();
                while (it.hasNext()) {
                    ApplicationListener<ApplicationEvent> listener = it.next();
                    sb.append(listener.getClass().getSimpleName()).append("(");
                    if( listener instanceof Ordered){
                        sb.append(((Ordered) listener).getOrder());
                    }else{
                        sb.append("0");
                    }
                    sb.append(")");
                    if( it.hasNext() ) sb.append(",");
                }
                eventLogger.debug("Publish {} of {} to {}", event.getClass().getSimpleName(),
                                 event.getSource().getClass().getSimpleName(), sb);
            }
            for (ApplicationListener<ApplicationEvent> listener : list) {
                listener.onApplicationEvent(event);
            }
        }
    }

    private static Logger getEventLogger(ApplicationEvent event){
        return LoggerFactory.getLogger(event.getClass());
    }

}