package hudson.plugins.dry.parser;

import hudson.plugins.analysis.util.model.AbstractAnnotation;
import hudson.plugins.analysis.util.model.FileAnnotation;
import hudson.plugins.analysis.util.model.Priority;
import hudson.plugins.dry.Messages;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;

import org.apache.commons.lang.StringUtils;

import de.java2html.converter.JavaSource2HTMLConverter;
import de.java2html.javasource.JavaSource;
import de.java2html.javasource.JavaSourceParser;
import de.java2html.options.JavaSourceConversionOptions;
import de.java2html.util.IllegalConfigurationException;
import edu.umd.cs.findbugs.annotations.SuppressWarnings;

/**
 * A serializable Java Bean class representing a duplicate code warning.
 *

 * Note: this class has a natural ordering that is inconsistent with equals.
 *

 *
 * @author Ulli Hafner
 */
public class DuplicateCode extends AbstractAnnotation {
    /** Unique identifier of this class. */
    private static final long serialVersionUID = -6231614169627992548L;
    /** Origin of the annotation. */
    public static final String ORIGIN = "dry";

    /** The links to the other code duplications. */
    @SuppressWarnings("Se")
    private final Set<DuplicateCode> links = new HashSet<DuplicateCode>();
    /** The duplicate source code fragment. */
    private String sourceCode;
    /**
     * Marks this duplication as derived. Derived duplications will not be shown in the user interface.
     *
     * @since 2.31
     */
    private boolean isDerived;

    /**
     * Creates a new instance of {@link DuplicateCode}.
     *
     * @param priority
     *            the priority of the warning
     * @param firstLine
     *            the starting line of the duplication
     * @param numberOfLines
     *            total number of duplicate lines
     * @param fileName
     *            name of the file that contains the duplication
     */
    public DuplicateCode(final Priority priority, final int firstLine, final int numberOfLines, final String fileName) {
        super(priority, Messages.DRY_Warning_Message(numberOfLines),
                firstLine, firstLine +  numberOfLines - 1, StringUtils.EMPTY, Messages.DRY_Warning_Type());

        setOrigin(ORIGIN);
        setFileName(fileName);
    }

    /**
     * Creates a new instance of {@link DuplicateCode}.
     *
     * @param priority
     *            the priority of the warning
     * @param firstLine
     *            the starting line of the duplication
     * @param numberOfLines
     *            total number of duplicate lines
     * @param fileName
     *            name of the file that contains the duplication
     * @param isDerived
     *            determines if this duplication is derived. Derived
     *            duplications will not be shown in the user interface
     */
    public DuplicateCode(final Priority priority, final int firstLine, final int numberOfLines, final String fileName, final boolean isDerived) {
        this(priority, firstLine, numberOfLines, fileName);

        this.isDerived = isDerived;
    }

    /**
     * Returns whether this duplication as derived. Derived duplications will
     * not be shown in the user interface.
     *
     * @return true if this duplication as derived, false if it is
     *         the master duplication
     */
    public boolean isDerived() {
        return isDerived;
    }

    @Override
    public String getType() {
        return Messages.DRY_Warning_Type();
    }

    @Override
    public String getMessage() {
        return Messages.DRY_Warning_Message(size());
    }

    /**
     * Returns the total number of duplicate lines.
     *
     * @return the number of duplicate lines
     */
    public int getNumberOfLines() {
        return getLineRanges().iterator().next().getEnd() - getPrimaryLineNumber() + 1;
    }

    /**
     * Returns the total number of duplicate lines.
     *
     * @return the number of duplicate lines
     */
    public int size() {
        return getNumberOfLines();
    }

    /**
     * Returns the total number of duplicate lines.
     *
     * @return the number of duplicate lines
     */
    public int length() {
        return getNumberOfLines();
    }

    /** {@inheritDoc} */
    public String getToolTip() {
        StringBuilder message = new StringBuilder();
        message.append("
");
        message.append(Messages.DRY_Duplications_Header());
        message.append("
");
        for (DuplicateCode duplication : links) {
            message.append("
");
            message.append(String.format("%s (%s)",
                    getKey(), duplication.getKey(), duplication.getPrimaryLineNumber(),
                    duplication.getLinkName(), duplication.getPrimaryLineNumber()));
            message.append("
");
        }
        message.append("
");
        message.append("
");
        return message.toString();
    }

    /**
     * Creates links to the specified collection of other code blocks.
     *
     * @param codeBlocks the code blocks to links to
     */
    public void linkTo(final List<DuplicateCode> codeBlocks) {
        links.addAll(codeBlocks);
        links.remove(this);
    }

    /**
     * Returns the links to the duplicated code in other files.
     *
     * @return the links
     */
    public Collection<DuplicateCode> getLinks() {
        return Collections.unmodifiableCollection(links);
    }

   /**
     * Returns the duplicate source code fragment.
     *
     * @return the duplicate source code fragment
     */
    public String getSourceCode() {
        return sourceCode;
    }

    /**
     * Returns the duplicate source code fragment as formatted HTML string.
     *
     * @return the duplicate source code fragment
     */
    public String getFormattedSourceCode() {
        try {
            JavaSource source = new JavaSourceParser().parse(new StringReader(sourceCode));
            JavaSource2HTMLConverter converter = new JavaSource2HTMLConverter();
            StringWriter writer = new StringWriter();
            JavaSourceConversionOptions options = JavaSourceConversionOptions.getDefault();
            options.setShowLineNumbers(false);
            options.setAddLineAnchors(false);
            converter.convert(source, options, writer);

            return writer.toString();
        }
        catch (IllegalConfigurationException exception) {
            return sourceCode;
        }
        catch (IOException exception) {
            return sourceCode;
        }
    }

    /**
     * Sets the duplicate source code fragment to the specified value.
     *
     * @param sourceCode the duplicate code fragment
     */
    public void setSourceCode(final String sourceCode) {
        this.sourceCode = sourceCode;
    }

    @Override
    public int hashCode() {
        int prime = 31; // NOCHECKSTYLE
        int result = super.hashCode();
        result = prime * result + ((sourceCode == null) ? 0 : sourceCode.hashCode());
        return result;
    }

    @Override
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }
        if (!super.equals(obj)) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        DuplicateCode other = (DuplicateCode)obj;
        if (sourceCode == null) {
            if (other.sourceCode != null) {
                return false;
            }
        }
        else if (!sourceCode.equals(other.sourceCode)) {
            return false;
        }
        return true;
    }

    /**
     * Returns the link with the specified hash code.
     *
     * @param linkHashCode
     *            the hash code of the linked annotation
     * @return the link with the specified hash code
     */
    public FileAnnotation getLink(final long linkHashCode) {
        for (FileAnnotation link : links) {
            if (link.getKey() == linkHashCode) {
                return link;
            }
        }
        throw new NoSuchElementException("Linked annotation not found: key=" + linkHashCode);
    }
}


/*******************************************************************************
 *
 *   Copyright (C) 2005 Sean Russell
 *
 *   This file is part of Broccoli.
 *
 *   Broccoli is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   Broccoli is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public License
 *   along with Broccoli.  If not, see <http://www.gnu.org/licenses/>.
 *
 ******************************************************************************/

package com.hi3project.vineyard.comm.stomp.gozirraws;

import java.util.Map;
import java.util.HashMap;
import java.io.*;
import java.net.Socket;
import javax.security.auth.login.LoginException;

/**
 * Implements a Stomp client connection to a Stomp server via the network.
 *
 * Example:
 *

 *   Client c = new Client( "localhost", 61626, "ser", "ser" );
 *   c.subscribe( "/my/channel", new Listener() { ... } );
 *   c.subscribe( "/my/other/channel", new Listener() { ... } );
 *   c.send( "/other/channel", "Some message" );
 *   // ...
 *   c.disconnect();
 *

 *
 * @see Stomp
 *
 * (c)2005 Sean Russell
 */
public class Client extends Stomp implements MessageReceiver {
  private Thread _listener;
  private OutputStream _output;
  private InputStream _input;
  private Socket _socket;

  /**
   * Connects to a server
   *
   * Example:
   *

   *   Client stomp_client = new Client( "host.com", 61626 );
   *   stomp_client.subscribe( "/my/messages" );
   *   ...
   *

   *
   * @see Stomp
   * @param server The IP or host name of the server
   * @param port The port the server is listening on
   */
  public Client( String server, int port, String login, String pass )
  throws IOException, LoginException {
    _socket = new Socket( server, port );
    _input = _socket.getInputStream();
    _output = _socket.getOutputStream();

    _listener = new Receiver( this, _input );
    _listener.start();

    // Connect to the server
    HashMap header = new HashMap();
    header.put( "login", login );
    header.put( "passcode", pass );
    transmit( Command.CONNECT, header, null );
    try {
      String error = null;
      while (!isConnected() && ((error=nextError()) == null)) {
        Thread.sleep(100);
      }
      if (error != null) throw new LoginException( error );
    } catch (InterruptedException e) {
    }
  }

  public boolean isClosed() { return _socket.isClosed(); }

  public void disconnect( Map header ) {
    if (!isConnected()) return;
    transmit( Command.DISCONNECT, header, null );
    _listener.interrupt();
    Thread.yield();
    try { _input.close();  } catch (IOException e) {/* We ignore these. */}
    try { _output.close(); } catch (IOException e) {/* We ignore these. */}
    try { _socket.close(); } catch (IOException e) {/* We ignore these. */}
    _connected = false;
  }

  /**
   * Transmit a message to the server
   */
  public void transmit( Command c, Map h, String b ) {
    try {
      Transmitter.transmit( c, h, b, _output );
    } catch (Exception e) {
      receive( Command.ERROR, null, e.getMessage() );
    }
  }

    @Override
    public String toString()
    {
        return "Client{" + "_socket=" + _socket + '}';
    }

}


package com.azaptree.services.commons.xml.jaxb;

/*
 * #%L
 * AZAPTREE-SERVICES-COMMONS
 * %%
 * Copyright (C) 2012 - 2013 AZAPTREE.COM
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */

import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

/**
 * Used to cache JAXBContexts at the JVM level
 *
 * @author alfio
 *
 */
public abstract class JAXBContextCache {

	private static final Map<String /* JAXB context path */, JAXBContext> jaxbContexts = new HashMap<>();

	/**
	 * If the JAXBContext is not cached, then it will create a new instance and cache it.
	 *
	 * @param contextPath
	 *            REQUIRED
	 * @return
	 */
	public static JAXBContext get(final String contextPath) {
		Assert.hasText(contextPath, "contextPath is required");
		JAXBContext ctx = jaxbContexts.get(contextPath);
		if (ctx == null) {
			try {
				ctx = JAXBContext.newInstance(contextPath);
			} catch (final JAXBException e) {
				throw new IllegalArgumentException("Failed to create JAXBContext - invalid JAXB context path: " + contextPath, e);
			}
			jaxbContexts.put(contextPath, ctx);
			LoggerFactory.getLogger(JAXBContextCache.class).info("cached : {}", contextPath);
		}
		return ctx;
	}

}


/**
 * @author XiongJie, Date: 13-12-3
 */
package com.myapp.caller;

import com.myapp.api.RouteAPI;
import org.springframework.remoting.rmi.RmiProxyFactoryBean;

/** Accept test caller */
public class CLI {

    /**
     * java -Drouter.port=1097 -Drouter.address=localhost -jar path/to/com.myapp.caller-1.0.0.jar jobId
     *
     * @param args jobId(mandatory)
     */
    public static void main(String[] args) {
        if (args.length < 1)
            throw new IllegalArgumentException("You must specify a job id");
        String jobId = args[0];
        RmiProxyFactoryBean factoryBean = new RmiProxyFactoryBean();
        factoryBean.setServiceInterface(RouteAPI.class);
        factoryBean.setServiceUrl(String.format("rmi://%s:%s/router",
                                                System.getProperty("router.address", "localhost"),
                                                System.getProperty("router.port", "1097")));
        factoryBean.afterPropertiesSet();
        RouteAPI router = (RouteAPI) factoryBean.getObject();
        Object result = router.perform(jobId);
        System.out.println("Got router response: " + result);
    }
}


/**
 * CSSBoxTreeBuilder.java
 *
 * Created on 24. 10. 2014, 23:52:25 by burgetr
 */
package org.fit.layout.cssbox;

import java.awt.Color;
import java.awt.Dimension;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.fit.cssbox.css.CSSNorm;
import org.fit.cssbox.css.DOMAnalyzer;
import org.fit.cssbox.io.DOMSource;
import org.fit.cssbox.io.DefaultDOMSource;
import org.fit.cssbox.io.DefaultDocumentSource;
import org.fit.cssbox.io.DocumentSource;
import org.fit.cssbox.layout.Box;
import org.fit.cssbox.layout.BrowserCanvas;
import org.fit.cssbox.layout.ElementBox;
import org.fit.cssbox.layout.Viewport;
import org.fit.cssbox.pdf.PdfBrowserCanvas;
import org.fit.layout.impl.DefaultBox;
import org.fit.layout.model.Box.DisplayType;
import org.fit.layout.model.Page;
import org.fit.layout.model.Rectangular;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import cz.vutbr.web.css.MediaSpec;

/**
 * This class implements building the box tree using the CSSBox rendering engine.
 *
 * @author burgetr
 */
public class CSSBoxTreeBuilder
{
    private static Logger log = LoggerFactory.getLogger(CSSBoxTreeBuilder.class);

    protected URL pageUrl;
    protected String pageTitle;

    /** The resulting page */
    protected PageImpl page;

    /** CSSBox viewport that represents the rendered page box tree */
    protected Viewport viewport;

    /** Requested page dimensions */
    protected Dimension pageSize;

    /** Use real visual bounds instead of the element content bounds for building the box hierarchy */
    protected boolean useVisualBounds;

    protected boolean preserveAux;

    /** Replace the images with their {@code alt} text */
    protected boolean replaceImagesWithAlt;

    /** a counter for assigning the box order */
    private int order_counter;

    private float zoom;

    public CSSBoxTreeBuilder(Dimension pageSize, boolean useVisualBounds, boolean preserveAux, boolean replaceImagesWithAlt)
    {
        this.pageSize = pageSize;
        this.useVisualBounds = useVisualBounds;
        this.preserveAux = preserveAux;
        this.replaceImagesWithAlt = replaceImagesWithAlt;
        this.zoom = 1.0f;
    }

    public float getZoom()
    {
        return zoom;
    }

    public void setZoom(float zoom)
    {
        this.zoom = zoom;
    }

    public void parse(URL url) throws IOException, SAXException
    {
        //render the page
        BrowserCanvas canvas = renderUrl(url, pageSize);
        viewport = canvas.getViewport();
        PageImpl pg = page = new PageImpl(pageUrl);
        pg.setTitle(pageTitle);

        //construct the box tree
        ElementBox rootbox = canvas.getViewport();
        BoxNode root = buildTree(rootbox);

        //initialize the page
        pg.setRoot(root);
        pg.setWidth(root.getWidth());
        pg.setHeight(root.getHeight());
    }

    public void parseList(List<URL> list) throws IOException, SAXException
    {
        int twidth = 0;
        int theight = 0;

        page = new PageImpl(list.get(0));
        DefaultBox main = new DefaultBox();
        main.setPage(page);
        main.setTagName("pageset");

        for (URL url : list)
        {
            log.info("Parsing: {}", url);
            //render the page
            BrowserCanvas canvas = renderUrl(url, pageSize);
            ElementBox rootbox = canvas.getViewport();
            BoxNode root = buildTree(rootbox);

            //wrap the page with a new block box
            DefaultBox pageBox = new DefaultBox();
            pageBox.setPage(page);
            pageBox.appendChild(root);
            pageBox.setTagName("page");
            pageBox.setDisplayType(DisplayType.BLOCK);
            pageBox.setBounds(new Rectangular(root.getBounds()));
            pageBox.setVisualBounds(new Rectangular(root.getBounds()));
            pageBox.setContentBounds(new Rectangular(root.getBounds()));
            pageBox.move(0, theight);
            pageBox.setBackgroundSeparated(true);

            log.info("Rendered: {}x{}", pageBox.getWidth(), pageBox.getHeight());

            //add to the root
            main.appendChild(pageBox);
            twidth = Math.max(twidth, pageBox.getWidth());
            theight = theight + pageBox.getHeight();
        }
        main.setBounds(new Rectangular(0, 0, twidth, theight));
        main.setVisualBounds(new Rectangular(0, 0, twidth, theight));

        //initialize the page
        page.setRoot(main);
        page.setWidth(twidth);
        page.setHeight(theight);
    }

    public void parse(String urlstring) throws MalformedURLException, IOException, SAXException
    {
        urlstring = urlstring.trim();
        if (urlstring.startsWith("http:") ||
            urlstring.startsWith("https:") ||
            urlstring.startsWith("ftp:") ||
            urlstring.startsWith("file:"))
        {
            parse(new URL(urlstring));
        }
        else if (urlstring.startsWith("list:"))
        {
            List<URL> list = loadList(urlstring.substring(5));
            parseList(list);
        }
        else
            throw new MalformedURLException("Unsupported protocol in " + urlstring);
    }

    /**
     * The resulting page model.
     * @return the page
     */
    public Page getPage()
    {
        return page;
    }

    /**
     * Obtains the CSSBox viewport of the rendered page.
     * @return the viewport or {@code null} when no page was rendered or multiple pages were rendered.
     */
    public Viewport getViewport()
    {
        return viewport;
    }

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

    protected BrowserCanvas renderUrl(URL url, Dimension pageSize) throws IOException, SAXException
    {
        DocumentSource src = new DefaultDocumentSource(url);
        pageUrl = src.getURL();
        InputStream is = src.getInputStream();
        String mime = src.getContentType();
        if (mime == null)
            mime = "text/html";
        int p = mime.indexOf(';');
        if (p != -1)
            mime = mime.substring(0, p).trim();
        log.info("File type: " + mime);

        if (mime.equals("application/pdf"))
        {
            PDDocument doc = loadPdf(is);
            BrowserCanvas canvas = new PdfBrowserCanvas(doc, null, pageSize, src.getURL());
            doc.close();
            pageTitle = "";
            return canvas;
        }
        else
        {
            DOMSource parser = new DefaultDOMSource(src);
            Document doc = parser.parse();
            pageTitle = findPageTitle(doc);

            String encoding = parser.getCharset();

            MediaSpec media = new MediaSpec("screen");
            //updateCurrentMedia(media);

            DOMAnalyzer da = new DOMAnalyzer(doc, src.getURL());
            if (encoding == null)
                encoding = da.getCharacterEncoding();
            da.setDefaultEncoding(encoding);
            da.setMediaSpec(media);
            da.attributesToStyles();
            da.addStyleSheet(null, CSSNorm.stdStyleSheet(), DOMAnalyzer.Origin.AGENT);
            da.addStyleSheet(null, CSSNorm.userStyleSheet(), DOMAnalyzer.Origin.AGENT);
            da.addStyleSheet(null, CSSNorm.formsStyleSheet(), DOMAnalyzer.Origin.AGENT);
            da.getStyleSheets();

            BrowserCanvas contentCanvas = new BrowserCanvas(da.getRoot(), da, src.getURL());
            contentCanvas.getConfig().setLoadImages(false);
            contentCanvas.getConfig().setLoadBackgroundImages(false);
            contentCanvas.getConfig().setLoadFonts(false);
            contentCanvas.getConfig().setReplaceImagesWithAlt(replaceImagesWithAlt);
            contentCanvas.createLayout(pageSize);

            src.close();

            return contentCanvas;
        }

    }

    private PDDocument loadPdf(InputStream is) throws IOException
    {
        PDDocument document = null;
        document = PDDocument.load(is);
        return document;
    }

    private String findPageTitle(Document doc)
    {
        NodeList heads = doc.getElementsByTagName("head");
        if (heads.getLength() > 0)
        {
            Element head = (Element) heads.item(0);
            NodeList titles = head.getElementsByTagName("title");
            if (titles.getLength() > 0)
            {
                final String ret = titles.item(0).getTextContent();
                return (ret == null) ? null : ret.trim();
            }
        }
        return "";
    }

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

    protected BoxNode buildTree(ElementBox rootbox)
    {
        //create the working list of nodes
        log.trace("LIST");
        Vector<BoxNode> boxlist = new Vector<BoxNode>();
        order_counter = 1;
        createBoxList(rootbox, boxlist);

        //create the tree
        if (useVisualBounds)
        {
            //two-phase algorithm considering the visual bounds
            log.trace("A1");
            BoxNode root = createBoxTree(rootbox, boxlist, true, true, true); //create a nesting tree based on the content bounds
            System.out.println("ZOOM " + zoom);
            log.trace("A2");
            Color bg = rootbox.getBgcolor();
            if (bg == null) bg = Color.WHITE;
            computeBackgrounds(root, bg); //compute the efficient background colors
            log.trace("A2.5");
            root.recomputeVisualBounds(); //compute the visual bounds for the whole tree
            log.trace("A3");
            root = createBoxTree(rootbox, boxlist, true, true, preserveAux); //create the nesting tree based on the visual bounds or content bounds depending on the settings
            root.recomputeVisualBounds(); //compute the visual bounds for the whole tree
            root.recomputeBounds(); //compute the real bounds of each node
            log.trace("A4");
            //root.applyTransforms(); //TODO test this first; actually the transform should be applied according to the drawing tree, not this tree
            return root;
        }
        else
        {
            //simplified algorihm - use the original box nesting
            BoxNode root = createBoxTree(rootbox, boxlist, false, true, true);
            Color bg = rootbox.getBgcolor();
            if (bg == null) bg = Color.WHITE;
            computeBackgrounds(root, bg); //compute the efficient background colors
            root.recomputeVisualBounds(); //compute the visual bounds for the whole tree
            root.recomputeBounds(); //compute the real bounds of each node
            root.applyTransforms();
            return root;
        }
    }

    /**
     * Recursively creates a list of all the visible boxes in a box subtree. The nodes are
     * added to the end of a specified list. The previous content of the list
     * remains unchanged. The 'viewport' box is ignored.
     * @param root the source root box
     * @param list the list that will be filled with the nodes
     */
    private void createBoxList(Box root, Vector<BoxNode> list)
    {
        if (root.isDisplayed())
        {
            if (!(root instanceof Viewport) && root.isVisible())
            {
                BoxNode newnode = new BoxNode(root, page, zoom);
                newnode.setOrder(order_counter++);
                list.add(newnode);
            }
            if (root instanceof ElementBox)
            {
                ElementBox elem = (ElementBox) root;
                for (int i = elem.getStartChild(); i < elem.getEndChild(); i++)
                    createBoxList(elem.getSubBox(i), list);
            }
        }
    }

    /**
     * Creates a tree of box nesting based on the content bounds of the boxes.
     * This tree is only used for determining the backgrounds.
     *
     * @param boxlist the list of boxes to build the tree from
     * @param useBounds when set to {@code true}, the full or visual bounds are used for constructing the tree
     * depending on the {@code useVisualBounds} parameter. Otherwise, the original box hierarchy is used.
     * @param useVisualBounds when set to {@code true} the visual bounds are used for constructing the tree. Otherwise,
     * the content bounds are used.
     * @param preserveAux when set to {@code true}, all boxes are preserved. Otherwise, only the visually
     * distinguished ones are preserved.
     */
    private BoxNode createBoxTree(ElementBox rootbox, Vector<BoxNode> boxlist, boolean useBounds, boolean useVisualBounds, boolean preserveAux)
    {
        //a working copy of the box list
        Vector<BoxNode> list = new Vector<BoxNode>(boxlist);

        //an artificial root node
        BoxNode root = new BoxNode(rootbox, page, zoom);
        root.setOrder(0);
        //detach the nodes from any old trees
        for (BoxNode node : list)
            node.removeFromTree();

        //when working with visual bounds, remove the boxes that are not visually separated
        if (!preserveAux)
        {
            for (Iterator<BoxNode> it = list.iterator(); it.hasNext(); )
            {
                BoxNode node = it.next();
                if (!node.isVisuallySeparated() || !node.isVisible())
                    it.remove();
            }
        }

        //let each node choose it's children - find the roots and parents
        for (BoxNode node : list)
        {
            if (useBounds)
                node.markNodesInside(list, useVisualBounds);
            else
                node.markChildNodes(list);
        }

        //choose the roots
        for (Iterator<BoxNode> it = list.iterator(); it.hasNext();)
        {
            BoxNode node = it.next();

            /*if (!full) //DEBUG
            {
               if (node.toString().contains("mediawiki") || node.toString().contains("globalWrapper"))
                    System.out.println(node + " => " + node.nearestParent);
            }*/

            if (node.isRootNode())
            {
                root.appendChild(node);
                it.remove();
            }
        }

        //recursively choose the children
        for (int i = 0; i < root.getChildCount(); i++)
            ((BoxNode) root.getChildAt(i)).takeChildren(list);

        return root;
    }

    /**
     * Computes efficient background color for all the nodes in the tree
     */
    private void computeBackgrounds(BoxNode root, Color currentbg)
    {
        Color newbg = root.getBackgroundColor();
        if (newbg == null)
            newbg = currentbg;
        root.setEfficientBackground(newbg);
        root.setBackgroundSeparated(!newbg.equals(currentbg));

        for (int i = 0; i < root.getChildCount(); i++)
            computeBackgrounds((BoxNode) root.getChildAt(i), newbg);
    }

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

    private List<URL> loadList(String filename)
    {
        List<URL> ret = new ArrayList<URL>();
        try
        {
            BufferedReader read = new BufferedReader(new FileReader(filename));
            String line;
            while ((line = read.readLine()) != null)
            {
                line = line.trim();
                if (!line.isEmpty() && !line.startsWith("#"))
                {
                    ret.add(new URL(line));
                }
            }
            read.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return ret;
    }

}


/**
 * @author XiongJie, Date: 13-12-2
 */
package net.happyonroad.builder;

import net.happyonroad.component.container.support.DefaultComponentRepository;
import net.happyonroad.component.core.support.ComponentUtils;
import net.happyonroad.component.core.support.DefaultComponent;
import net.happyonroad.component.core.support.Dependency;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.io.RawInputStreamFacade;

import java.io.*;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;

import static net.happyonroad.component.core.support.ComponentUtils.relativePath;
import static org.apache.commons.io.FileUtils.listFiles;
import static org.apache.commons.lang.StringUtils.substringAfter;

/**
 * 打包一个基于Spring Component 框架的项目
 */
@SuppressWarnings("unused unchecked")
@Mojo(name = "package", inheritByDefault = false, defaultPhase = LifecyclePhase.PACKAGE)
public class SpringComponentPackaging extends SpringComponentCopyDependencies {
    private static final Pattern          INTERPOLATE_PTN_A = Pattern.compile("\\$\\{([^}]+)\\}", Pattern.MULTILINE);
    private static final Pattern          INTERPOLATE_PTN_B = Pattern.compile("#\\{([^}]+)\\}", Pattern.MULTILINE);
    private static final Charset          UTF8              = Charset.forName("UTF-8");
    private static final SimpleDateFormat sdf               = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @Parameter(defaultValue = "1099")
    int        appPort;
    @Parameter(defaultValue = "")
    String     jvmOptions;
    @Parameter
    String     appName;
    @Parameter
    Properties properties;
    @Parameter
    String     propertyFiles;
    @Parameter
    String     logbackFile;
    @Parameter
    String     folders;
    @Parameter
    String     files;
    @Parameter
    String     frontendNodeModules;
    //Debug port
    @Parameter(defaultValue = "0")
    private int     debug;
    //Jmx port
    @Parameter(defaultValue = "0")
    private int     jmx;
    @Parameter
    private String  appPrefix;
    @Parameter(defaultValue = "true")
    private Boolean wrapper;
    @Parameter
    private String  repositoryBase;

    private String appBoot, appTarget;

    private static String lineSeparator = System.getProperty("os.name").contains("Windows") ? "\r\n" : "\n";

    public void doExecute() throws MojoExecutionException {
        getLog().info("Hello, I'm packaging to " + getTargetFolder().getPath());

        if (StringUtils.isNotBlank(repositoryBase)) {
            this.repositoryBase = FilenameUtils.normalize(this.repositoryBase);
        }

        initAppParams();

        prepareFolders();

        prepareFiles();

        File appFolder = new File(getTargetFolder(), "app");
        File libFolder = new File(getTargetFolder(), "lib");
        File repositoryFolder = getRepositoryFolder();

        copyTargets(libFolder);

        copyDependencies(libFolder);

        //清除与jar重复的pom文件
        reducePoms(libFolder);

        //将repository/lib下面的第三方包与lib下面的归并
        // 因为现在是先extending，而后再packing
        reduceDependencies(libFolder, repositoryFolder, true);

        //将 lib/ repository/ 目录下的pom文件归并到一个压缩文件中
        //以免用户修改其中的内容，导致系统不能运行
        sealPoms(libFolder);
        sealPoms(repositoryFolder);

        moveBootstrapJar();

        Map<String, Object> replaces = createAppOptions();

        interpolateScripts(replaces);

        generateScripts(replaces);

        generateWrapper(replaces);

        if (StringUtils.isNotBlank(frontendNodeModules)) {
            //从各个jar包里面抽取前端程序
            generateFrontendResources();
            //根据需要，将前端程序从各个jar包中删除，降低系统运行时的负担
            reduceFrontendResources();
        }

        // Move app jars from lib to app
        moveAppJars(libFolder, appFolder);

        cleanEmptyFolders(appFolder, libFolder, repositoryFolder);

    }

    @Override
    protected void copyFile(File artifact, File destFile) throws MojoExecutionException {
        String relativePath = relativePath(destFile);
        File repositoryFolder = getRepositoryFolder();
        File preventFile = null;
        if (StringUtils.isNotBlank(repositoryBase)) {
            boolean contains = false;
            try {
                contains = FilenameUtils.directoryContains(repositoryBase, artifact.getPath());
            } catch (IOException e) {
                //skip
            }
            if (contains) {
                //这个文件是Repository文件，不放到lib下
                destFile = new File(repositoryFolder, relativePath);
                File libFolder = new File(getTargetFolder(), "lib");
                preventFile = new File(libFolder, relativePath);
            }
        }
        if (preventFile == null)
            preventFile = new File(repositoryFolder, relativePath);

        try {
            copyFile(artifact, destFile, preventFile);
        } catch (IOException e) {
            throw new MojoExecutionException("Can't move exist " + preventFile.getPath()
                                             + " to " + destFile.getPath(), e);
        }
    }

    /**
     *
把artifact文件copy到目标文件，如果另外一个相应的文件存在，则移动之

     *
     * @param artifact 原始文件
     * @param dest     目标文件
     * @param prevent  防止的文件
     */
    protected void copyFile(File artifact, File dest, File prevent) throws IOException, MojoExecutionException {
        if (dest.exists()) {
            if (prevent.exists())
                //noinspection ResultOfMethodCallIgnored
                prevent.delete();
        } else {
            if (prevent.exists())
                org.apache.commons.io.FileUtils.moveFile(prevent, dest);
            else
                super.copyFile(artifact, dest);
        }
    }

    private void initAppParams() throws MojoExecutionException {
        if (appName == null || StringUtils.isBlank(appName)) {
            appName = project.getName() == null ? project.getArtifactId() : project.getName();
        }
        appName = interpolate(appName, getProjectProperties(), 'A');
        if (properties == null) {
            properties = new Properties();
        }
        appTarget = String.format("%s/%s@%s", project.getGroupId(), project.getArtifactId(), project.getVersion());
        try {
            System.setProperty("app.home", getTargetFolder().getCanonicalPath());
        } catch (IOException e) {
            System.setProperty("app.home", getTargetFolder().getAbsolutePath());
        }
    }

    private void prepareFolders() throws MojoExecutionException {
        if (!getTargetFolder().exists()) {
            FileUtils.mkdir(getTargetFolder().getAbsolutePath());
        }
        String[] subFolders = {"bin", "boot", "app", "lib", "lib/poms", "config", "repository", "logs"};
        for (String relativePath : subFolders) {
            try {
                prepareFolder(relativePath);
            } catch (IOException e) {
                throw new MojoExecutionException("Can't prepare sub folder " +
                                                 relativePath + " in " + getTargetFolder().getPath());
            }
        }
        if (StringUtils.isNotBlank(folders)) {
            for (String path : StringUtils.split(folders, ",")) {
                try {
                    File folder = new File(path);
                    File dest = new File(getTargetFolder(), folder.getName());
                    FileUtils.copyDirectoryStructure(folder, dest);
//                    String[] propertyFiles = FileUtils.getFilesFromExtension(dest.getAbsolutePath(), new String[]{"properties"});
//                    Map<String, Object> projectProperties = getProjectProperties();
//                    for (String propertyFile : propertyFiles) {
//                        changeFileA(propertyFile, projectProperties);
//                    }
                } catch (IOException e) {
                    getLog().error("Can't copy user specified folder: " + path + " because of:" + e.getMessage());
                }
            }
        }
    }

    private void prepareFiles() throws MojoExecutionException {
        if (StringUtils.isNotBlank(files)) {
            for (String path : StringUtils.split(files, ",")) {
                try {
                    File file = new File(path.trim());
                    String relativePath = substringAfter(file.getAbsolutePath(),
                                                         getProject().getBasedir().getAbsolutePath() + "/");
                    File dest = new File(getTargetFolder(), relativePath);
                    FileUtils.copyFile(file, dest);
                } catch (IOException e) {
                    getLog().error("Can't copy user specified folder: " + path + " because of:" + e.getMessage());
                }
            }
        }

    }

    private void moveBootstrapJar() throws MojoExecutionException {
        File libPath = new File(getTargetFolder(), "lib");
        List<File> bootJars;
        try {
            bootJars = FileUtils.getFiles(libPath, "net.happyonroad/spring-component-framework@*.jar", null);
        } catch (IOException e) {
            throw new MojoExecutionException("Can't search " + libPath.getPath() +
                                             " for net.happyonroad/spring-component-framework@*.jar");
        }
        if (bootJars.isEmpty()) {
            throw new MojoExecutionException("You should configure a runtime dependency to " +
                                             "spring-component-framework!");
        }
        if (bootJars.size() > 1) {
            throw new MojoExecutionException("There are more than one net.happyonroad/spring-component-framework");
        }
        File bootJar = bootJars.get(0);
        File destJar = new File(getTargetFolder(), "boot/" + bootJar.getName());
        try {
            FileUtils.rename(bootJar, destJar);
        } catch (IOException e) {
            throw new MojoExecutionException("Can't move boot jar:" + e.getMessage());
        }
        String bootPom = bootJar.getPath().replace(".jar", ".pom");
        try {
            FileUtils.forceDelete(bootPom);
        } catch (IOException e) {
            getLog().warn("Can't delete unnecessary boot pom " + bootPom + " :" + e.getMessage());
        }
    }

    private void moveAppJars(File libFolder, File appFolder) throws MojoExecutionException {
        Collection<File> appJars = getAppJars(libFolder);
        for (File appJar : appJars) {
            // app jar file = target/lib/group/artifact@version.jar
            // new jar file = target/app/group/artifact@version.jar
            String fileName = FilenameUtils.getName(appJar.getAbsolutePath());
            String groupName = FilenameUtils.getName(appJar.getParent());
            File newFile = new File(appFolder, groupName + "/" + fileName);
            try {
                FileUtils.rename(appJar, newFile);
            } catch (IOException e) {
                throw new MojoExecutionException("Can't move " + fileName + " to app folder", e);
            }
        }
    }

    private void interpolateScripts(Map<String, Object> replaces) throws MojoExecutionException {
        try {
            Collection<File> scripts = listFiles(new File(getTargetFolder(), "bin"), new String[]{"sh", "bat"}, true);
            if (!scripts.isEmpty()) {
                for (File script : scripts) {
                    changeFileA(script.getPath(), replaces);
                    chmod(script);
                }
            }
        } catch (IOException e) {
            throw new MojoExecutionException("Can't interpolate script", e);
        }
    }

    private void generateScripts(Map<String, Object> replaces) throws MojoExecutionException {
        try {
            String[] resourceNames = {"start.bat", "start.sh", "follow.sh", "stop.bat", "stop.sh", "check.sh"};
            for (String resource : resourceNames) {
                if (!FileUtils.fileExists(new File(getTargetFolder(), "bin/" + resource).getPath())) {
                    copyResource(resource, "bin", replaces);
                    chmod(resource, "bin");
                }
            }
            if (logbackFile != null) {
                if( logbackFile.startsWith("!")) {
                    FileUtils.copyFile(new File(logbackFile.substring(1)),
                                       new File(getTargetFolder(), "config/logback.xml"));
                }else{
                    copyPropertyFile(new File(logbackFile), replaces);
                }
            } else {
                copyResource("logback.xml", "config", replaces);
            }
            if (propertyFiles != null) {
                for (String path : propertyFiles.split(",")) {
                    copyPropertyFile(new File(path), replaces);
                }
            } else {
                File propertiesFile = new File(getTargetFolder(), "config/" + project.getArtifactId() + ".properties");
                FileOutputStream fos = new FileOutputStream(propertiesFile);
                properties.store(fos, appName + " Properties");
                fos.close();
            }
        } catch (IOException e) {
            throw new MojoExecutionException("Can't generate script", e);
        }

    }

    private Map<String, Object> createAppOptions() {
        List<String> bootJars;
        try {
            bootJars = FileUtils.getFileNames(new File(getTargetFolder(), "boot"),
                                              "spring-component-framework@*.jar",
                                              null, false);
        } catch (IOException e) {
            return new HashMap<String, Object>();
        }
        appBoot = "boot/" + bootJars.get(0);
        Map<String, Object> replaces = getProjectProperties();
        if (jvmOptions == null) jvmOptions = "";
        // don't put port(maybe replaced at last)
        // it may affect sed
        if (debug > 0) {
            jvmOptions += " -agentlib:jdwp=transport=dt_socket,server=y,address=" + debug + ",suspend=n";
        }
        if (jmx > 0) {
            jvmOptions += " -Dcom.sun.management.jmxremote" +
                          " -Dcom.sun.management.jmxremote.port=" + jmx +
                          " -Dcom.sun.management.jmxremote.local.only=false" +
                          " -Dcom.sun.management.jmxremote.authenticate=false" +
                          " -Dcom.sun.management.jmxremote.ssl=false";
        }
        if (StringUtils.isNotBlank(appPrefix)) {
            jvmOptions += " -Dapp.prefix=" + appPrefix;
        }
        jvmOptions = jvmOptions.replaceAll("\n", "");
        jvmOptions = jvmOptions.replaceAll("\\s+", " ");
        replaces.put("jvm.options", jvmOptions);
        return replaces;
    }

    private void generateWrapper(Map<String, Object> replaces) throws MojoExecutionException {
        if (!wrapper) {
            getLog().warn("Skip generate wrapper");
            return;
        }
        try {
            copyResources("net/happyonroad/wrapper/bin/", "bin");
            copyResources("net/happyonroad/wrapper/boot/", "boot");
            copyResources("net/happyonroad/wrapper/conf/", "config");

            String appHome = (String) replaces.get("app.home");
            String appName = (String) replaces.get("app.name");
            String mainExecutor = appHome + "/bin/" + StringUtils.lowerCase(appName);

            renameFile(appHome + "/bin/main", mainExecutor);
            chmod(new File(mainExecutor));

            String[] jvmOptionArray = jvmOptions.split(" ");
            for (int i = 0; i < jvmOptionArray.length; i++) {
                String jvmOption = jvmOptionArray[i];
                replaces.put("jvm.option." + (i + 6), jvmOption);
            }

            changeFileB(mainExecutor, replaces);

            changeFileA(appHome + "/config/wrapper.conf", replaces);
        } catch (IOException e) {
            throw new MojoExecutionException("Can't copy classpath resources, check the .index file!");
        }

    }

    private void generateFrontendResources() throws MojoExecutionException {
        DefaultComponentRepository repository = new DefaultComponentRepository(getTargetFolder().getAbsolutePath());
        repository.start();

        Properties mappings = new Properties();
        File[] jarArray = getApplicationJars();
        try {
            repository.sortCandidates(jarArray);
        } catch (Exception ex) {
            throw new MojoExecutionException("Can't sort the files by component dependencies", ex);
        }
        for (File componentJar : jarArray) {
            extractFrontendResources(componentJar, mappings);
        }

        if (FileUtils.fileExists(frontendNodeModules)) {
            try {
                FileUtils.copyDirectoryStructure(new File(frontendNodeModules),
                                                 new File(getTargetFolder(), "webapp/frontend/node_modules"));
            } catch (IOException e) {
                getLog().warn("Can't copy node modules", e);
            }
        }
        FileOutputStream mappingStream = null;
        try {
            mappingStream = new FileOutputStream(new File(getTargetFolder(), "webapp/frontend/.mappings"));
            StringBuilder sb = new StringBuilder();
            sb.append("{\n");
            Iterator<String> names = mappings.stringPropertyNames().iterator();
            while (names.hasNext()) {
                String name = names.next();
                sb.append(String.format("  \"%s\" : \"%s\"", name, mappings.getProperty(name)));
                if (names.hasNext()) sb.append(",\n");
            }
            sb.append("\n}\n");
            IOUtils.write(sb.toString(), mappingStream);
        } catch (Exception ex) {
            throw new MojoExecutionException("Can't generate mapping file");
        } finally {
            IOUtils.closeQuietly(mappingStream);
        }
    }

    private File[] getApplicationJars() {
        Collection<File> libJars = getAppJars(new File(getTargetFolder(), "lib"));
        Collection<File> repositoryJars = getAppJars(new File(getTargetFolder(), "repository"));
        List<File> allJars = new ArrayList<File>();
        allJars.addAll(libJars);
        allJars.addAll(repositoryJars);
        return allJars.toArray(new File[allJars.size()]);
    }

    private Collection<File> getAppJars(File folder) {
        Collection<File> jars = folder.exists() ? listFiles(folder, new String[]{"jar"}, true) : Collections.EMPTY_SET;
        Iterator<File> it = jars.iterator();
        while (it.hasNext()) {
            File file = it.next();
            String relativePath = ComponentUtils.relativePath(file);
            if (!DefaultComponent.isApplication(relativePath)) {
                it.remove();
            }
        }
        return jars;
    }

    private void extractFrontendResources(File componentJar, Properties mappings) throws MojoExecutionException {
        getLog().debug("Extract frontend resources from:" + componentJar);
        try {
            Map<String, Object> replaces = new HashMap<String, Object>();
            replaces.put("project.version", project.getVersion());
            replaces.put("build.timestamp", sdf.format(new Date()));
            Dependency dep = Dependency.parse(componentJar);
            String source = dep.getGroupId() + "." + dep.getArtifactId();
            JarFile jarFile = new JarFile(componentJar);
            if (jarFile.getEntry("frontend") == null) return;
            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                if (entry.getName().startsWith("frontend")) {
                    File file = new File(getTargetFolder(), "webapp/" + entry.getName());
                    InputStream stream = jarFile.getInputStream(entry);
                    if (entry.isDirectory())
                        FileUtils.mkdir(file.getAbsolutePath());
                    else {
                        if (entry.getName().endsWith("config.js")) {
                            List<String> strings = IOUtils.readLines(stream);
                            String content = StringUtils.join(strings, "\n");
                            content = interpolate(content, replaces, 'A');
                            org.apache.commons.io.FileUtils.write(file, content);
                        } else {
                            org.apache.commons.io.FileUtils.copyInputStreamToFile(stream, file);
                        }
                        mappings.setProperty(entry.getName(), source);
                    }
                }
            }
            jarFile.close();
        } catch (Exception e) {
            throw new MojoExecutionException("Can't extract frontend resources from " + componentJar.getName(), e);
        }
    }

    private void reduceFrontendResources() throws MojoExecutionException {
        File[] jarArray = getApplicationJars();
        for (File jar : jarArray) {
            JarFile jarFile = null;
            File tempJarFolder = null;
            try {
                jarFile = new JarFile(jar);
                String reduceFrontend = jarFile.getManifest().getMainAttributes().getValue("Reduce-Frontend");
                if ("false".equalsIgnoreCase(reduceFrontend)) continue;
                ZipEntry detailEntry = jarFile.getEntry("META-INF/INDEX.DETAIL");
                if (detailEntry == null) continue;

                List<String> lines = IOUtils.readLines(jarFile.getInputStream(detailEntry));
                int reduces = reduceIndexes(lines);
                if (reduces == 0) continue;

                tempJarFolder = extractJar(jar);
                org.apache.commons.io.FileUtils.deleteDirectory(new File(tempJarFolder, "frontend"));
                FileOutputStream outStream = new FileOutputStream(new File(tempJarFolder, detailEntry.getName()));
                IOUtils.writeLines(lines, "\n", outStream);
                outStream.close();
                createJar(tempJarFolder, jar);
            } catch (IOException e) {
                throw new MojoExecutionException("Can't reduce jar file", e);
            } finally {
                IOUtils.closeQuietly(jarFile);
                org.apache.commons.io.FileUtils.deleteQuietly(tempJarFolder);
            }

        }

    }

    private int reduceIndexes(List<String> lines) {
        Iterator<String> it = lines.iterator();
        int count = 0;
        while (it.hasNext()) {
            String line = it.next();
            if (line.startsWith("frontend")) {
                it.remove();
                count++;
            }
        }
        return count;
    }

    private void renameFile(String from, String to) throws IOException {
        FileUtils.rename(new File(from), new File(to));
    }

    private Map<String, Object> getProjectProperties() {
        Map<String, Object> replaces = new HashMap<String, Object>();
        Properties projectProperties = project.getProperties();
        for (String property : projectProperties.stringPropertyNames()) {
            replaces.put(property, projectProperties.getProperty(property));
        }
        replaces.put("app.home", getTargetFolder().getAbsolutePath());
        replaces.put("app.name", replaceSpaces(appName));
        replaces.put("app.port", appPort);
        replaces.put("app.boot", appBoot);
        replaces.put("app.target", appTarget);
        return replaces;
    }

    private Object replaceSpaces(String appName) {
        return appName.replaceAll("\\s", "_");
    }

    private void copyPropertyFile(File file, Map<String, Object> replaces) throws IOException {
        FileInputStream fis = new FileInputStream(file);
        List<String> lines = IOUtils.readLines(fis, UTF8);
        String interpolated;
        if (file.getName().toLowerCase().endsWith(".properties")) {
            interpolated = interpolate(lines, replaces, 'A');
        } else {
            String content = StringUtils.join(lines, lineSeparator);
            interpolated = interpolate(content, replaces, 'A');
        }
        String relative = substringAfter(file.getAbsolutePath(),
                                         project.getBasedir().getAbsolutePath() + File.separator);
        FileUtils.fileWrite(new File(getTargetFolder(), relative), interpolated);
    }

    private void changeFileA(String path, Map<String, Object> replaces) throws IOException {
        changeFile(path, replaces, 'A');
    }

    private void changeFileB(String path, Map<String, Object> replaces) throws IOException {
        changeFile(path, replaces, 'B');
    }

    private void changeFile(String path, Map<String, Object> replaces, char mode) throws IOException {
        File file = new File(path);
        FileInputStream fis = new FileInputStream(file);
        List<String> lines = IOUtils.readLines(fis, UTF8);
        String interpolated;
        if (file.getName().toLowerCase().endsWith(".properties")) {
            interpolated = interpolate(lines, replaces, mode);
        } else {
            String content = StringUtils.join(lines, lineSeparator);
            interpolated = interpolate(content, replaces, mode);
        }
        FileUtils.fileWrite(file, interpolated);
    }

    private void copyResource(String resource, String path, Map<String, Object> replaces) throws IOException {
        String content = read(resource);
        String interpolated = interpolate(content, replaces, 'A');
        FileUtils.fileWrite(new File(getTargetFolder(), path + "/" + resource), interpolated);
    }

    private void copyResources(String resourcePath, String path) throws IOException {
        ClassLoader cl = getClass().getClassLoader();
        InputStream indexStream = cl.getResourceAsStream(resourcePath + ".index");
        try {
            List<String> names = IOUtils.readLines(indexStream, UTF8);
            for (String name : names) {
                InputStream stream = cl.getResourceAsStream(resourcePath + name);
                try {
                    FileUtils.copyStreamToFile(new RawInputStreamFacade(stream),
                                               new File(getTargetFolder(), path + "/" + name));
                } finally {
                    stream.close();
                }
            }
        } finally {
            indexStream.close();
        }
    }

    private void chmod(String resource, String path) {
        File script = new File(getTargetFolder(), path + "/" + resource);
        chmod(script);
    }

    private void chmod(File script) {
        if (!SystemUtils.IS_OS_WINDOWS) {
            try {
                Process process = Runtime.getRuntime().exec(new String[]{"chmod", "u+x", script.getAbsolutePath()});
                process.waitFor();
                if (process.exitValue() != 0) {
                    System.err.println(
                            "Can't chmod u+x for " + script.getPath() + ", exit value =" + process.exitValue());
                }
            } catch (IOException e) {
                System.err.println("Can't chmod u+x for " + script.getPath() + ", because of:" + e.getMessage());
            } catch (InterruptedException e) {
                System.err.println("Can't chmod u+x for " + script.getPath() + ", because of:" + e.getMessage());
            }
        }
    }

    private String read(String resource) throws IOException {
        InputStream stream = getClass().getResourceAsStream(resource);
        List lines = IOUtils.readLines(stream, UTF8);
        return StringUtils.join(lines, lineSeparator);
    }

    //interpolate xml, bat file content with ${}
    private String interpolate(String content, Map<String, Object> replaces, char mode) {
        Pattern pattern = mode == 'A' ? INTERPOLATE_PTN_A : INTERPOLATE_PTN_B;
        Matcher m = pattern.matcher(content);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            String variable = m.group(1);
            Object replace = replaces.get(variable);
            String replacement = replace == null ? "" : replace.toString();
            if (pattern.matcher(replacement).find())
                replacement = interpolate(replacement, replaces, mode);
            try {
                m.appendReplacement(sb, replacement);
            } catch (Exception e) {
                System.err.println(e.getMessage());
            }
        }
        m.appendTail(sb);
        return sb.toString().trim();
    }

    // interpolate properties file like: http.port=the.default.value
    private String interpolate(List<String> lines, Map<String, Object> replaces, char mode) {
        for (int i = 0; i < lines.size(); i++) {
            String line = lines.get(i);
            if (!line.trim().startsWith("#") && line.contains("=")) {
                String[] array = line.split("=");
                String key = array[0].trim();
                // string like: neo4j.user= split array length == 1
                String value;
                if (array.length == 2) {
                    value = array[1];
                } else if (array.length == 1) {
                    value = "";
                } else {
                    continue;//don't touch them
                }
                value = interpolate(value, replaces, mode);
                if (replaces.containsKey(key)) {
                    value = replaces.get(key).toString();
                }
                lines.set(i, key + "=" + value);
            }
        }
        return StringUtils.join(lines, lineSeparator);
    }

    private void prepareFolder(String relativePath) throws IOException {
        File subFolder = new File(getTargetFolder(), relativePath);
        //noinspection StatementWithEmptyBody
        if (subFolder.exists()) {
            // don't clean it, because extension is build before release now
            // FileUtils.cleanDirectory(subFolder);
        } else {
            FileUtils.forceMkdir(subFolder);
        }
    }
}


/**
 * Developer: Kadvin Date: 14/11/18 下午3:27
 */
package net.happyonroad.component.core.support;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import sun.net.www.protocol.component.Handler;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * The component url stream handler factory
 */
public class ComponentURLStreamHandlerFactory implements URLStreamHandlerFactory, IOFileFilter{
    private static ComponentURLStreamHandlerFactory instance;
    // component id to real file
    private        Map<String, File>                componentFiles;

    public static ComponentURLStreamHandlerFactory getFactory() {
        if (instance == null) instance = new ComponentURLStreamHandlerFactory();
        return instance;
    }

    ComponentURLStreamHandlerFactory() {
        instance = this;
    }

    @Override
    public URLStreamHandler createURLStreamHandler(String protocol) {
        return protocol.equals("component") ? new Handler() : null;
    }

    /**
     * Set the real file of component url
     * @param url the component url
     * @param realFile the real file
     */
    public void setMappingFile(URL url, String realFile){
        if (componentFiles == null) initFastIndexes("app", "lib", "repository");
        File file = new File(realFile);
        componentFiles.put(url.getFile(), file);
    }

    /**
     * Get the real file of component url
     * @param url the component url
     * @return the real file
     * @throws IOException
     */
    public File getMappingFile(URL url) throws IOException {
        if (componentFiles == null) initFastIndexes("app", "lib", "repository");
        String fileName = FilenameUtils.normalize(url.getFile(), true);
        if( !fileName.endsWith(".jar") ) fileName = fileName + ".jar";
        if( fileName.startsWith("boot/") || fileName.startsWith("boot\\") ){
            fileName = "net.happyonroad/" + FilenameUtils.getName(fileName);
        }else if(fileName.startsWith("lib/") || fileName.startsWith("lib\\")){/* Only 3rd lib file will be put into jar classpath*/
            //正常的组件component url里面肯定不是lib开头；
            // 但是 spring-component-framework的Class-Path指定的那些url被normalize之后却会如此
            fileName  = ComponentUtils.relativePath(fileName);
        }
        File componentFile = componentFiles.get(fileName);
        if( componentFile == null )
        {
            // 当第一次建立的快速索引中没有相应文件时
            // 尝试看lib下后来有没有相应的文件
            componentFile = guessFile(url, "app", "lib", "repository");
            if( componentFile == null )
                throw new IOException("there is no component named as " + fileName);
        }
        return componentFile;
    }

    protected Map<String, File> initFastIndexes(String... folders) {
        componentFiles = new HashMap<String, File>();
        String home = getAppHome();

        File bootFolder = new File(home, "boot");
        if( bootFolder.isDirectory() ){
            Collection<File> frameworkJars = FileUtils.listFiles(bootFolder, this, null);
            for (File frameworkJar : frameworkJars) {
                String relativeName = "net.happyonroad/" + frameworkJar.getName();
                componentFiles.put(relativeName, frameworkJar);
            }
        }
        for (String folder : folders) {
            File libFolder = new File(home, folder);
            if( libFolder.isDirectory()){
                Collection<File> libFiles = FileUtils.listFiles(libFolder, new String[]{"jar"}, true);
                for (File libFile : libFiles) {
                    if (libFile.isFile() && libFile.getName().endsWith(".jar")) {
                        String relativeName = ComponentUtils.relativePath(libFile);
                        componentFiles.put(relativeName, libFile);
                    }
                }
            }
        }
        return componentFiles;
    }

    protected File guessFile(URL url, String... folders){
        for (String folder : folders) {
            File file = new File(getAppHome(), folder + "/" + url.getFile());
            if( file.exists() ) {
                componentFiles.put(url.getFile(), file);
                return file;
            }
        }
        return null;
    }

    private String getAppHome() {
        return System.getProperty("app.home", System.getProperty("user.dir"));
    }

    @Override
    public boolean accept(File file) {
        return file.getName().contains("spring-component-framework@");
    }

    @Override
    public boolean accept(File dir, String name) {
        return false;
    }
}


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

import net.happyonroad.spring.exception.ServiceConfigurationException;
import net.happyonroad.spring.service.ServiceImporter;
import org.springframework.beans.factory.annotation.Autowired;

/**
 *
抽象的使用其他服务的应用配置

 * 主要功能是：
 *

 *
注入 Service Importer

 *
提供 简便的 imports方法

 *

 */
public abstract class AbstractUserConfig {
    @Autowired
    protected ServiceImporter importer;

    /**
     *
导入某个服务

     *
     * @param serviceClass 服务的接口
     * @param <T>          服务的类型
     * @return 服务的实例
     * @throws net.happyonroad.spring.exception.ServiceConfigurationException 找不到服务时抛出
     */
    protected <T> T imports(Class<T> serviceClass) throws ServiceConfigurationException{
        return importer.imports(serviceClass);
    }

    /**
     *
导入某个服务

     *
     * @param serviceClass 服务的接口
     * @param hint         服务的提示
     * @param <T>          服务的类型
     * @return 服务的实例
     * @throws net.happyonroad.spring.exception.ServiceConfigurationException 找不到服务时抛出
     */
    protected <T> T imports(Class<T> serviceClass, String hint) throws ServiceConfigurationException{
        return importer.imports(serviceClass, hint);
    }

}


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

import net.happyonroad.component.core.Component;
import net.happyonroad.component.core.ComponentVersion;
import net.happyonroad.component.core.Versionize;
import net.happyonroad.component.core.exception.InvalidComponentNameException;
import net.happyonroad.component.core.exception.InvalidVersionSpecificationException;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 版本依赖
 */
public class Dependency implements Versionize{

    private static Pattern digitPattern        = Pattern.compile("(^\\d+)(.+)?");
    private static Pattern rangeFeaturePattern = Pattern.compile("[,||\\(|\\)|\\[\\]]+");

    private String groupId;

    private String artifactId;

    /*package*/ String version;//核心版本，如: 1.0.0, for test

    private VersionRange range;//版本范围，如 [1.0.0,)

    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 String scope;//See Component.SCOPE_XXX

    private Boolean         optional;
    private Set<Exclusion> exclusions;

    public Dependency() {
    }

    public Dependency(String groupId, String artifactId) {
        this(groupId, artifactId, null);
    }

    public Dependency(String groupId, String artifactId, String version) {
        this.groupId = groupId;
        this.artifactId = artifactId;
        setVersion(version);
    }

    public String getGroupId() {
        return groupId;
    }

    public String getArtifactId() {
        return artifactId;
    }

    public String getVersion() {
        return version;
    }

    public String getClassifier() {
        return classifier;
    }

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

    public String getType() {
        return type;
    }

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

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

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

    public void setClassifier(String classifier) {
        if(classifier == null || classifier.length() == 0)
            this.classifier = null;
        else
            this.classifier = classifier;
    }

    public void setVersion(String version) {
        if(version != null ){
            if(rangeFeaturePattern.matcher(version).find()){
                VersionRange range;
                try {
                    range = VersionRange.createFromVersionSpec(version);
                } catch (InvalidVersionSpecificationException e) {
                    throw new RuntimeException("Can't parse version range: " + version, e);
                }
                if( range.hasRestrictions() ){
                    this.version = null;
                    this.range = range;
                }else{
                    this.version = range.getRecommendedVersion().toString();
                }
            }else{
                if( version.contains("-") ){
                    String[] vac = version.split("-");
                    this.version = vac[0];
                    this.classifier = vac[1];
                    return;
                }
                String[] versionAndClassifier = splitClassifierFromVersion(version);
                this.version = versionAndClassifier[0];
                this.setClassifier(versionAndClassifier[1]);
            }
        }else{
            this.version = null;
        }
    }

    public boolean isOptional() {
        if(optional == null ) return false;
        return optional;
    }

    public void setOptional(boolean optional) {
        this.optional = optional;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    public boolean isTest(){
        return Component.SCOPE_TEST.equalsIgnoreCase(getScope());
    }

    public boolean isProvided(){
        return Component.SCOPE_PROVIDED.equalsIgnoreCase(getScope());
    }

    @SuppressWarnings("UnusedDeclaration")
    public boolean isRuntime(){
        return Component.SCOPE_RUNTIME.equalsIgnoreCase(getScope());
    }

    public boolean isCompile(){
        return Component.SCOPE_COMPILE.equalsIgnoreCase(getScope()) || getScope() == null;
    }

    public Set<Exclusion> getExclusions() {
        return exclusions;
    }

    public void setExclusions(Set<Exclusion> exclusions) {
        this.exclusions = exclusions;
    }

    @Override
    public String toString() {
        String gs = groupId == null ? "<undefined>" : groupId;
        String as = artifactId == null ? "<undefined>" : artifactId;
        String vs = version != null && version.length() > 0 ? "@" + version : null;
        if(vs == null ){
            if( range != null )
                vs = "@" + range.toString();
            else
                vs = "@<undefined>";
        }
        String cs = classifier != null && classifier.length() > 0 ? "-" + classifier : "";
        String ts = type != null && type.length() > 0 ? "." + type : "";
        return String.format("%s/%s%s%s%s", gs, as, vs, cs, ts);
    }

    public static Dependency parse(File file) throws InvalidComponentNameException {
        String fileName = FilenameUtils.getName(file.getAbsolutePath());
        String type = FilenameUtils.getExtension(fileName);
        String baseName;
        if( "pom".equalsIgnoreCase(type) || "jar".equalsIgnoreCase(type) ){
            baseName = FilenameUtils.getBaseName(fileName);
        }else{
            baseName = fileName;
            type = null;
        }
        if( !baseName.contains("@") )
            throw new InvalidComponentNameException("The artifact file should be named as artifactId@version, " +
                                                    "instead of " + file.getPath());
        String groupId = FilenameUtils.getName(file.getParent());
        String artifactId = StringUtils.substringBefore(baseName, "@");
        String version = StringUtils.substringAfter(baseName, "@");
        String classifier = null;
        String[] versionAndClassifier = splitClassifierFromVersion(version);
        if( versionAndClassifier.length > 1 ){
            version = versionAndClassifier[0];
            classifier = versionAndClassifier[1];
        }
        Dependency dependency = new Dependency(groupId, artifactId, version);
        dependency.setClassifier(classifier);
        dependency.setType(type);
        return dependency;
    }
    /**
     * 根据文件全名称解析依赖信息
     *
     * @param fullName 全名称，形如： groupId/artifactId@version.type
     * @return 依赖信息
     */
    public static Dependency parse(String fullName) throws InvalidComponentNameException {
        return parse(new File(fullName));
    }

    private static void removeLastChar(StringBuilder string) {
        if (string.length() > 0)
            string.deleteCharAt(string.length() - 1);
    }

    static String[] splitClassifierFromVersion(String version) {
        StringBuilder classifier = new StringBuilder();
        // 分离version中的classifier，注意，version并不总是 1.0.0这样，可能：
        //   1.1 -> 1.1.0
        //   甚至1.1.2.3这种形式
        //   我认为，暂时不应该有简化到只有一个数字的版本号，如：1
        String[] versions = version.split("-");
        if( versions.length > 1 ) {
            String[] again = splitClassifierFromVersion(versions[0]);
            if( again.length > 1 && StringUtils.isNotBlank(again[1]) ){
                return new String[]{again[0], again[1] + "-" + join(versions, 1, "-")};
            }else{
                return new String[]{versions[0], join(versions, 1, "-")};
            }
        }
        versions = version.split("\\.");
        StringBuilder pureVersion = new StringBuilder();
        int i = 0;
        for (; i < versions.length; i++) {
            String v = versions[i];
            Matcher dm = digitPattern.matcher(v);
            if (dm.matches()) {
                pureVersion.append(dm.group(1)).append(".");
                String closingClassifier = dm.group(2);
                if(closingClassifier != null){
                    if(closingClassifier.startsWith("-")){
                        closingClassifier = closingClassifier.substring(1, closingClassifier.length());
                    }
                    if(classifier.length() > 0 ){
                        classifier.insert(0, closingClassifier + "-");
                    }else{
                        classifier.append(closingClassifier);
                    }
                    i = versions.length;
                    break;
                }
            } else {
                break;
            }
        }
        removeLastChar(pureVersion);
        // pureVersion = 1.0.0
        StringBuilder classifierInVersion = new StringBuilder();
        for (; i < versions.length; i++) {
            String c = versions[i];
            classifierInVersion.append(c).append(".");
        }
        removeLastChar(classifierInVersion);
        if (classifierInVersion.length() > 0) {
            boolean separate = (classifier.length() > 0);
            classifier.insert(0, classifierInVersion);
            if (separate)
                classifier.insert(classifierInVersion.length(), "-");
        }
        return new String[]{pureVersion.toString(), classifier.toString()};
    }

    private static String join(String[] versions, int start, String sep) {
        StringBuilder sb = new StringBuilder();
        for(int i=start;i<versions.length;i++)
        {
            sb.append(versions[i]).append("-");
        }
        removeLastChar(sb);
        return sb.toString();
    }

    /**
     * 判断某个组件是否满足特定依赖信息
     * <pre>
     * 如， dependency1 = {groupId: dnt, artifactId: component}
     *     dependency2 = {groupId: dnt, artifactId: component, version: 1.0.0}
     *     那么: dependency1.accept(dependency2) = true
     *     反之: dependency2.accept(dependency1) = false
     * </pre>
     * @param componentOrDependency 被判断的组件或者其他依赖
     * @return 是否依赖
     */
    public boolean accept(Versionize componentOrDependency) {
        boolean accept = getGroupId().equals(componentOrDependency.getGroupId()) &&
                         getArtifactId().equals(componentOrDependency.getArtifactId());
        if (!accept) return false;
        if (getVersion() != null) {
            //有version需要，暂时不知道是否需要支持version的表达式，maven自身好像没有这个机制
            accept = getVersion().equals(componentOrDependency.getVersion());
            if (!accept) return false;
        }
        if( range != null ){
            // Temp solution
            if( componentOrDependency.getVersion() == null ) return true;
            accept = range.containsVersion(new ComponentVersion(componentOrDependency.getVersion()));
            if (!accept) return false;
        }
/*
        if( getClassifier() != null ){
            accept = getClassifier().equals(componentOrDependency.getClassifier());
            if (!accept) return false;
        }
*/
/*
        if( getType() != null ){
            accept = getType().equals(componentOrDependency.getType());
            if (!accept) return false;
        }
*/
        return true;
    }

    public boolean exclude(Versionize componentOrDependency){
        if( exclusions == null || exclusions.isEmpty()) return false;
        for (Exclusion exclusion : exclusions) {
            if(exclusion.cover(componentOrDependency)) return true;
        }
        return false;
    }

    public boolean conflict(Dependency dependency){
        return getGroupId().equals(dependency.getGroupId()) &&
                getArtifactId().equals(dependency.getArtifactId());
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Dependency)) {
            return false;
        }

        Dependency that = (Dependency) o;

        if (!artifactId.equals(that.artifactId)) {
            return false;
        }
        if (!groupId.equals(that.groupId)) {
            return false;
        }
        //noinspection RedundantIfStatement
        if (version != null ? !version.equals(that.version) : that.version != null) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = groupId.hashCode();
        result = 31 * result + artifactId.hashCode();
        result = 31 * result + (version != null ? version.hashCode() : 0);
        return result;
    }

    /**
     * 为某个依赖补齐信息
     * @param parentDefined 父组件中定义的依赖信息
     */
    public void merge(Dependency parentDefined) {
        if(optional == null ){
            setOptional(parentDefined.isOptional());
        }
        if(version == null){
            setVersion(parentDefined.getVersion());
        }
        if(classifier == null ){
            setClassifier(parentDefined.getClassifier());
        }
        if(scope == null ){
            setScope(parentDefined.getScope());
        }
        if(exclusions == null ){
            setExclusions(parentDefined.getExclusions());
        }
    }

    /**
     * 根据组件的上下文，替换依赖中的信息
     * @param component 所属的组件
     */
    public void interpolate(DefaultComponent component) {
        if(groupId != null) this.groupId = component.interpolate(groupId);
        if(artifactId != null) this.artifactId = component.interpolate(artifactId);
        //暂时主要处理一些启动相关的关键属性，如version
        if(version != null ) this.setVersion(component.interpolate(version)) ;
        if(classifier != null ) this.classifier = component.interpolate(classifier);
        if(scope != null ) this.scope = component.interpolate(scope);
    }

    public boolean hasExclusions() {
        return exclusions != null && !exclusions.isEmpty();
    }

    public void exclude(Collection<Exclusion> exclusions) {
        if( exclusions == null ) return;
        if( this.exclusions == null ) this.exclusions = new HashSet<Exclusion>();
        this.exclusions.addAll(exclusions);
    }
}


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

import net.happyonroad.component.container.ComponentLoader;
import net.happyonroad.component.container.ComponentRepository;
import net.happyonroad.component.container.feature.ApplicationFeatureResolver;
import net.happyonroad.component.container.feature.StaticFeatureResolver;
import net.happyonroad.component.core.*;
import net.happyonroad.component.core.support.Dependency;
import net.happyonroad.spring.service.MutableServiceRegistry;
import net.happyonroad.spring.service.ServiceRegistry;
import net.happyonroad.spring.support.CombinedMessageSource;
import net.happyonroad.spring.support.DefaultServiceHelper;
import net.happyonroad.spring.support.DefaultServiceRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericApplicationContext;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import static org.apache.commons.lang.time.DurationFormatUtils.formatDurationHMS;
import static org.springframework.context.support.AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME;

/**
 * 组件加载
 */
public class DefaultComponentLoader implements ComponentLoader, ComponentContext {
    private Logger logger = LoggerFactory.getLogger(DefaultComponentLoader.class);

    /*package*/ final List<FeatureResolver>    featureResolvers;
    /*package*/ final List<FeatureResolver>    reverseResolvers;
    /*package*/ final Map<Component, Features> loadedFeatures;
    /*package*/ final MutableServiceRegistry   registry;
    /*package*/ final DefaultServiceHelper     helper;
    /*package*/ final ComponentRepository      repository;
    /*package*/ final Set<Component>           loading, unloading;
    /*package*/ ApplicationContext             context;

    public DefaultComponentLoader(ComponentRepository repository,
                                  FeatureResolver... resolvers) {
        this.repository = repository;
        loadedFeatures = new ConcurrentHashMap<Component, Features>();
        registry = new DefaultServiceRegistry();
        helper = new DefaultServiceHelper(registry);
        featureResolvers = new LinkedList<FeatureResolver>();
        reverseResolvers = new LinkedList<FeatureResolver>();
        loading = new LinkedHashSet<Component>();
        unloading = new LinkedHashSet<Component>();
        //注册缺省的特性解析器
        registerResolver(new StaticFeatureResolver());
        registerResolver(new ApplicationFeatureResolver());
        //注册构造时传入的扩展Feature Resolvers
        for (FeatureResolver resolver : resolvers) {
            registerResolver(resolver);
        }
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        //将全局的注册表也注册进去
        beanFactory.registerSingleton("springServiceRegistry", getRegistry());
        //ServiceHelper( Importer/Exporter )
        beanFactory.registerSingleton("serviceHelper", getServiceHelper());
        //将Component Loader也注册进去
        beanFactory.registerSingleton("springComponentLoader", getComponentLoader());
        //将Component Repository也注册进去
        beanFactory.registerSingleton("springComponentRepository", getComponentRepository());
        //系统全局使用该message source
        beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, new CombinedMessageSource());
        GenericApplicationContext context = new GenericApplicationContext(beanFactory);
        context.setDisplayName("Component Root Context");
        context.refresh();
        this.context = context;
    }

    /**
     * 注册一个扩展特性解析器
     *
     * @param resolver 解析器
     */
    public void registerResolver(FeatureResolver resolver) {
        resolver.bind(this);
        featureResolvers.add(resolver);
        reverseResolvers.add(resolver);
        Collections.sort(featureResolvers, new LoadOrder());
        Collections.sort(reverseResolvers, new UnloadOrder());
    }

    @Override
    public void removeResolver(FeatureResolver resolver) {
        featureResolvers.remove(resolver);
        reverseResolvers.remove(resolver);
        Collections.sort(featureResolvers, new LoadOrder());
        Collections.sort(reverseResolvers, new UnloadOrder());
    }

    @Override
    public <T extends FeatureResolver> T getFeatureResolver(String name) {
        for (FeatureResolver resolver : featureResolvers) {
            if (resolver.getName().equals(name))
                //noinspection unchecked
                return (T) resolver;
        }
        return null;
    }

    @Override
    public boolean isLoaded(String componentId) {
        for(Component component : loadedFeatures.keySet()){
            if( component.getId().equals(componentId))
                return true;
        }
        return false;
    }

    @Override
    public boolean isLoaded(Component component) {
        return loadedFeatures.containsKey(component);
    }

    public ApplicationContext getRootContext() {
        return context;
    }

    @Override
    public <T> T getFeature(Component component, String name) {
        Features features = loadedFeatures.get(component);
        if (features == null) return null;
        return features.getFeature(name);
    }

    @Override
    public ClassLoader getLibraryFeature(Component component) {
        Features features = loadedFeatures.get(component);
        if (features == null) return null;
        return features.getLibraryFeature();
    }

    @Override
    public ApplicationContext getApplicationFeature(Component component) {
        Features features = loadedFeatures.get(component);
        if (features == null) return null;
        return features.getApplicationFeature();
    }

    @Override
    public List<ApplicationContext> getApplicationFeatures() {
        List<Component> components = repository.getComponents();
        List<ApplicationContext> contexts = new LinkedList<ApplicationContext>();
        for (Component component : components) {
            ApplicationContext feature = getApplicationFeature(component);
            if( feature != null ) contexts.add(feature);
        }
        return contexts;
    }

    @Override
    public ServiceRegistry getRegistry() {
        return registry;
    }

    @Override
    public DefaultServiceHelper getServiceHelper() {
        return helper;
    }

    @Override
    public ComponentLoader getComponentLoader() {
        return this;
    }

    @Override
    public ComponentRepository getComponentRepository() {
        return repository;
    }

    /**
     * 自顶向下的加载组件
     *
     * @param component 被加载的组件
     */
    @Override
    public void load(Component component) throws Exception {
        if (isLoaded(component)) return;
        logger.debug("Before loading {}", component);
        //首先加载父组件
        if (component.getParent() != null) {
            //上面会自动检测是否已经加载，防止重复加载
            load(component.getParent());
        }
        //而后加载依赖的组件
        for (Component depended : component.getDependedComponents()) {
            //上面会自动检测是否已经加载，防止重复加载
            Dependency textualDependency = new Dependency(depended.getGroupId(), depended.getArtifactId());
            Dependency actualDependency = component.getDependency(textualDependency);
            if( actualDependency.isProvided() || actualDependency.isTest() )
                continue;
            load(depended);
        }
        quickLoad(component);
        //最后，加载自身
        logger.debug("After  loaded  {}", component);
    }

    /**
     * 剥洋葱一样的卸载组件
     *
     * @param component 被卸载的组件
     */
    @Override
    public void unload(Component component) {
        if (!isLoaded(component)) return;
        logger.debug("Before unloading {}", component);
        //先卸载自身
        quickUnload(component);
        //再卸载依赖
        for (Component depended : component.getDependedComponents()) {
            //上面会自动检测是否已经加载，防止重复加载
            unload(depended);
        }
        //最后卸载其父组件
        if (component.getParent() != null) {
            //上面会自动检测是否已经加载，防止重复加载
            unload(component.getParent());
        }
        logger.debug("After  unloaded  {}", component);
    }

    boolean isLoading(Component component) {
        return loading.contains(component);
    }

    boolean isUnloading(Component component) {
        return unloading.contains(component);
    }

    void loading(Component component) {
        loading.add(component);
    }

    void unloading(Component component) {
        unloading.add(component);
    }

    void loaded(Component component) {
        loading.remove(component);
    }

    void unloaded(Component component) {
        unloading.remove(component);
    }

    @Override
    public void registerFeature(Component component, String name, Object feature) {
        Features features = loadedFeatures.get(component);
        if (features == null) {
            features = new Features(name, feature);
            loadedFeatures.put(component, features);
        } else {
            features.setFeature(name, feature);
        }
    }

    @Override
    public Object removeFeature(Component component, String name) {
        Features features = loadedFeatures.get(component);
        if (features != null) {
            return features.remove(name);
        }
        return null;
    }

    /**
     * 实际卸载一个组件，不考虑依赖情况
     *
     * @param component 被加载的组件
     * @throws IOException 加载过程中的IO错误
     */
    public void quickLoad(Component component) throws Exception {
        if (component.isAggregating()) {
            logger.trace("Needn't real load aggregating component {}", component);
            loadedFeatures.put(component, FeatureResolver.AggregatingFlag);
        } else if (component.isPlain()) {
            logger.trace("Needn't real load plain component {}", component);
            loadedFeatures.put(component, FeatureResolver.PlainFlag);
        } else {
            if (isLoading(component)) return;
            long start = System.currentTimeMillis();
            loading(component);
            ComponentResource resource = component.getResource();
            if (resource == null) {
                throw new IOException("The component " + component + " without resource");
            }
            logger.info("Loading {}", component);
            List<FeatureResolver> resolvers = new ArrayList<FeatureResolver>(featureResolvers.size());
            for (FeatureResolver featureResolver : featureResolvers) {
                featureResolver.applyDefaults(component);
                if (featureResolver.hasFeature(component)) {
                    resolvers.add(featureResolver);
                }
            }

            for (FeatureResolver featureResolver : resolvers) {
                featureResolver.beforeResolve(component);
            }
            for (FeatureResolver featureResolver : resolvers) {
                featureResolver.resolve(component);
            }
            for (FeatureResolver featureResolver : resolvers) {
                featureResolver.afterResolve(component);
            }
            loaded(component);
            logger.info("Loaded  {} ({})", component, formatDurationHMS(System.currentTimeMillis() - start));
        }
    }

    /**
     * 实际卸载一个组件
     *
     * @param component 被卸载的组件
     */
    public void quickUnload(Component component) {
        if (component.isAggregating()) {
            logger.trace("Remove aggregating component {}", component);
            loadedFeatures.remove(component);
        } else if (component.isPlain()) {
            logger.trace("Remove plain component {}", component);
            loadedFeatures.remove(component);
        } else {
            if (isUnloading(component)) return;
            long start = System.currentTimeMillis();
            unloading(component);
            logger.info("Unloading {}", component);
            for (FeatureResolver resolver : reverseResolvers) {
                if (resolver.hasFeature(component)) {
                    resolver.release(component);
                }
            }
            loadedFeatures.remove(component);
            unloaded(component);
            logger.info("Unloaded  {} ({})", component, formatDurationHMS(System.currentTimeMillis() - start));
        }
    }

    static class LoadOrder implements java.util.Comparator<FeatureResolver> {
        @Override
        public int compare(FeatureResolver resolver1, FeatureResolver resolver2) {
            return resolver1.getLoadOrder() - resolver2.getLoadOrder();
        }
    }

    static class UnloadOrder implements java.util.Comparator<FeatureResolver> {
        @Override
        public int compare(FeatureResolver resolver1, FeatureResolver resolver2) {
            return resolver1.getUnloadOrder() - resolver2.getUnloadOrder();
        }
    }
}