@SuppressWarnings("serial")
public class ArtifactContext implements Serializable, ContentOptions {
    public static final String CAR = ".car";
    public static final String JAR = ".jar";
    public static final String JS_MODEL = "-model.js";
    public static final String JS = ".js";
    public static final String DART = ".dart";
    public static final String DART_MODEL = "-dartmodel.json";
    public static final String RESOURCES = "module-resources";
    public static final String SRC = ".src";
    public static final String MAVEN_SRC = "-sources.jar";
    public static final String DOCS = "module-doc";
    public static final String MODULE_PROPERTIES = "module.properties";
    public static final String MODULE_XML = "module.xml";
    public static final String SHA1 = ".sha1";
    public static final String INFO = ".info";
    public static final String SCRIPTS_ZIPPED = ".scripts.zip";
    public static final String ZIP = ".zip";

    // IMPORTANT: Makes sure the elements in this array are ordered in such
    // a way that no ambiguities can occur when matching them one at a time.
    // NB: SHA1 and ZIP are not part of this list because they are supposed
    // to be "composed" with other suffixes
    private static final String fileSuffixes[] = {
        CAR, JAR, JS_MODEL, JS, DART, DART_MODEL, RESOURCES, SRC, MAVEN_SRC,
        DOCS, SCRIPTS_ZIPPED
    };
    
    private static final String composedSuffixes[] = {
        ZIP, SHA1
    };
    
    private static final String fileNames[] = {
        MODULE_PROPERTIES, MODULE_XML
    };
    
    private static final String directoryNames[] = {
        RESOURCES, DOCS
    };

    public static final String[] allSuffixes() {
        ArrayList<String> all = new ArrayList<>(fileSuffixes.length + fileNames.length + directoryNames.length);
        all.addAll(Arrays.asList(fileSuffixes));
        all.addAll(Arrays.asList(fileNames));
        all.addAll(Arrays.asList(directoryNames));
        String[] tmp = new String[all.size()];
        return all.toArray(tmp);
    }
    
    private String name;
    private String version;
    private String[] suffixes = {CAR};
    private boolean localOnly;
    private boolean ignoreSHA;
    private boolean ignoreCache;
    private boolean throwErrorIfMissing;
    private boolean forceOperation;
    private boolean forceDescriptorCheck;
    private boolean ignoreDependencies;
    private ArtifactCallback callback;
    private Repository repository;

    public ArtifactContext(String name, String version) {
        this.name = name;
        this.version = version;
    }

    public ArtifactContext(String name, String version, String... suffixes) {
        this(name, version);
        this.suffixes = suffixes;
    }

    private ArtifactContext(String name, String version, Repository repository, String... suffixes) {
        this(name, version);
        this.suffixes = suffixes;
        this.repository = repository;
    }

    public ArtifactContext() {
    }

    // Returns true if the suffix that was passed can have
    // an associated .sha1 suffix
    private static boolean isShaAllowed(String suffix) {
        if (MODULE_PROPERTIES.equals(suffix) || MODULE_XML.equals(suffix)
                || suffix.endsWith(SHA1) || isDirectoryName(suffix)) {
            return false;
        }
        return true;
    }

    /**
     * Returns the SHA1 version of the current context or null
     * if the current context is already an SHA1 artifact or
     * if the artifact doesn't allow or need signing
     * @return A new SHA1 context
     */
    public ArtifactContext getSha1Context() {
        if (suffixes.length == 1) {
            if (isShaAllowed(suffixes[0])) {
                String[] sha1Suffixes = { suffixes[0] + SHA1 };
                return new ArtifactContext(name, version, sha1Suffixes);
            }
        }
        return null;
    }

    /**
     * Returns the ZIP version of the current context or null
     * if the current context is already a ZIP artifact or
     * if the artifact doesn't allow or need zipping
     * @return A new ZIP context
     */
    public ArtifactContext getZipContext() {
        if (suffixes.length == 1) {
            if (!suffixes[0].endsWith(ZIP) && isDirectoryName(suffixes[0])) {
                String[] sha1Suffixes = { suffixes[0] + ZIP };
                return new ArtifactContext(name, version, sha1Suffixes);
            }
        }
        return null;
    }

    public ArtifactContext getSuffixContext(String... suffixes) {
        ArtifactContext ac = copy();
        ac.setSuffixes(suffixes);
        return ac;
    }

    public ArtifactContext getDocsContext() {
        return getSuffixContext(DOCS);
    }

    public ArtifactContext getResourcesContext() {
        return getSuffixContext(RESOURCES);
    }

    public ArtifactContext getModuleProperties() {
        return getSuffixContext(MODULE_PROPERTIES);
    }

    public ArtifactContext getModuleXml() {
        return getSuffixContext(MODULE_XML);
    }

    public ArtifactContext getSibling(ArtifactResult result, String... suffixes) {
        ArtifactContext sibling = new ArtifactContext(result.name(), result.version(), result.repository(), suffixes);
        sibling.copySettingsFrom(this);
        return sibling;
    }

    public void toNode(Node node) {
        if (node instanceof OpenNode) {
            final OpenNode on = (OpenNode) node;
            on.addNode(INFO, this);
        }
    }

    public static ArtifactContext fromNode(Node node) {
        final Node ac = (node instanceof OpenNode) ? ((OpenNode) node).peekChild(INFO) : node.getChild(INFO);
        return ac != null ? ac.getValue(ArtifactContext.class) : null;
    }

    public static void removeNode(Node node) {
        if (node instanceof OpenNode) {
            final OpenNode on = (OpenNode) node;
            if (on.peekChild(INFO) != null) {
                on.removeNode(INFO);
            }
        }
    }

    public boolean isMaven(){
        return name != null
                && (name.startsWith("maven:")
                        || name.indexOf(':') != -1);
    }
    
    public String getName() {
        if(name != null && name.startsWith("maven:"))
            return name.substring("maven:".length());
        return name;
    }

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

    public String getVersion() {
        return version;
    }

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

    public String[] getSuffixes() {
        return suffixes;
    }

    public void setSuffixes(String... suffixes) {
        this.suffixes = suffixes;
    }

    public String getSingleSuffix() {
        if (suffixes.length != 1) {
            throw new RepositoryException("ArtifactContext should have a single suffix");
        }
        return suffixes[0];
    }

    public static String getSuffixFromNode(Node node) {
        String fileName = node.getLabel();
        return getSuffixFromFilename(fileName);
    }

    /**
     * Returns the artifact "suffix" of the file name that gets passed.
     * The word "suffix" is in quotes because it can also return one
     * of the "full name" results like "module-docs" or "module.properties"
     */
    public static String getSuffixFromFilename(String fileName) {
        String suffix = getSuffixFromFilename_(fileName);
        if (SHA1.equals(suffix)) {
            fileName = fileName.substring(0, fileName.length() - 5);
            return (fileName.isEmpty()) ? SHA1 : getSuffixFromFilename(fileName) + SHA1;
        } else if (ZIP.equals(suffix)) {
            fileName = fileName.substring(0, fileName.length() - 4);
            return (fileName.isEmpty()) ? ZIP : getSuffixFromFilename(fileName) + ZIP;
        }
        return suffix;
    }

    private static String getSuffixFromFilename_(String fileName) {
        for (String suffix : fileNames) {
            if (fileName.equals(suffix)) {
                return suffix;
            }
        }
        for (String suffix : directoryNames) {
            if (fileName.equals(suffix)) {
                return suffix;
            }
        }
        for (String suffix : fileSuffixes) {
            if (fileName.endsWith(suffix)) {
                return suffix;
            }
        }
        for (String suffix : composedSuffixes) {
            if (fileName.endsWith(suffix)) {
                return suffix;
            }
        }
        throw new RepositoryException("Unknown suffix in " + fileName);
    }

    public static String getArtifactName(String name, String version, String suffix) {
        if (suffix.endsWith(SHA1)) {
            return getArtifactName(name, version, suffix.substring(0, suffix.length() - 5)) + SHA1;
        } else {
            if (isDirectoryName(suffix) || isFullName(suffix))
                return suffix;
            else if (RepositoryManager.DEFAULT_MODULE.equals(name))
                return name + suffix;
            else
                return name + "-" + version + suffix;
        }
    }

    /**
     * Returns true if the filename that was passed is one of the
     * directory artifact names. Eg. "module-docs"
     */
    public static boolean isDirectoryName(String fileName) {
        for (String suffix : directoryNames) {
            if (fileName.equals(suffix)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns true if the filename that was passed is one of the
     * non-suffix artifact names. Eg. "module.properties"
     */
    private static boolean isFullName(String fileName) {
        for (String suffix : fileNames) {
            if (fileName.equals(suffix)) {
                return true;
            }
        }
        for (String suffix : directoryNames) {
            if (fileName.equals(suffix + ZIP) || fileName.equals(suffix + ZIP + SHA1)) {
                return true;
            }
        }
        return false;
    }

    public boolean isLocalOnly() {
        return localOnly;
    }

    public void setLocalOnly(boolean localOnly) {
        this.localOnly = localOnly;
    }

    public boolean isIgnoreSHA() {
        return ignoreSHA;
    }

    public void setIgnoreSHA(boolean ignoreSHA) {
        this.ignoreSHA = ignoreSHA;
    }

    public boolean isIgnoreCache() {
        return ignoreCache;
    }

    public void setIgnoreCache(boolean ignoreCache) {
        this.ignoreCache = ignoreCache;
    }

    public boolean isThrowErrorIfMissing() {
        return throwErrorIfMissing;
    }

    public void setThrowErrorIfMissing(boolean throwErrorIfMissing) {
        this.throwErrorIfMissing = throwErrorIfMissing;
    }

    public boolean isForceOperation() {
        return forceOperation;
    }

    public void setForceOperation(boolean forceOperation) {
        this.forceOperation = forceOperation;
    }

    public boolean isForceDescriptorCheck() {
        return forceDescriptorCheck;
    }

    public void setForceDescriptorCheck(boolean forceDescriptorCheck) {
        this.forceDescriptorCheck = forceDescriptorCheck;
    }

    public boolean isIgnoreDependencies() {
        return ignoreDependencies;
    }

    public void setIgnoreDependencies(boolean ignoreDependencies) {
        this.ignoreDependencies = ignoreDependencies;
    }

    public ArtifactCallback getCallback() {
        return callback;
    }

    public void setCallback(ArtifactCallback callback) {
        this.callback = callback;
    }

    public Repository getSearchRepository() {
        return repository;
    }

    @Override
    public String toString() {
        StringBuilder str = new StringBuilder();
        str.append(getName()).append("-").append(getVersion());
        if (suffixes.length == 1 && !isDirectoryName(suffixes[0]) && !isFullName(suffixes[0])) {
            str.append(suffixes[0]);
        } else {
            str.append("(");
            boolean first = true;
            for (String s : suffixes) {
                if (!first) {
                    str.append("|");
                }
                str.append(s);
                first = false;
            }
            str.append(")");
        }
        return str.toString();
    }

    public boolean forceOperation() {
        return isForceOperation();
    }

    public boolean forceDescriptorCheck() {
        return isForceDescriptorCheck();
    }
    
    public ArtifactContext copy() {
        ArtifactContext ac = new ArtifactContext(name, version, suffixes);
        ac.copySettingsFrom(this);
        ac.repository = repository;
        return ac;
    }

    public ArtifactContext copySettingsFrom(ArtifactContext ac) {
        localOnly = ac.localOnly;
        ignoreSHA = ac.ignoreSHA;
        ignoreCache = ac.ignoreCache;
        throwErrorIfMissing = ac.throwErrorIfMissing;
        forceOperation = ac.forceOperation;
        forceDescriptorCheck = ac.forceDescriptorCheck;
        ignoreDependencies = ac.ignoreDependencies;
        callback = ac.callback;
        return ac;
    }

    @Override
    public boolean equals(Object obj) {
        // WARNING: this is only about equality of coordinates, as used in Overrides
        if(obj == this)
            return true;
        if(obj instanceof ArtifactContext == false)
            return false;
        ArtifactContext other = (ArtifactContext) obj;
        return Objects.equals(getName(), other.getName())
                && Objects.equals(version, other.version);
    }
    
    @Override
    public int hashCode() {
        int hash = 17;
        hash = 37 * hash + (getName() != null ? getName().hashCode() : 0);
        hash = 37 * hash + (version != null ? version.hashCode() : 0);
        return hash;
    }
} public class ModuleQuery {
    protected String name;
    protected Type type;
    protected Retrieval retrieval;
    private Long start;
    private Long count;
    private long[] pagingInfo;
    private Integer binaryMajor;
    private Integer binaryMinor;
    private String memberName;
    private boolean memberSearchPackageOnly;
    private boolean memberSearchExact;

    public enum Type {
        SRC(ArtifactContext.SRC),
        CAR(ArtifactContext.CAR),
        JAR(ArtifactContext.JAR),
        JVM(ArtifactContext.CAR, ArtifactContext.JAR),
        JS(ArtifactContext.JS),
        DART(ArtifactContext.DART),
        CODE(ArtifactContext.CAR, ArtifactContext.JAR, ArtifactContext.JS),
        CEYLON_CODE(ArtifactContext.CAR, ArtifactContext.JS),
        ALL(ArtifactContext.allSuffixes());
        
        private String[] suffixes;

        Type(String... suffixes){
            this.suffixes = suffixes;
        }

        public String[] getSuffixes() {
            return suffixes;
        }
        
        public boolean includes(String... suffs) {
            return Arrays.asList(suffixes).containsAll(Arrays.asList(suffs));
        }
    }
    
    public enum Retrieval {
        ANY, ALL
    }
    
    public ModuleQuery(String name, Type type) {
        this(name, type, Retrieval.ANY);
    }
    
    public ModuleQuery(String name, Type type, Retrieval retrieval) {
        this.name = name;
        this.type = type;
        this.retrieval = retrieval;
    }
    
    public String getName() {
        return name;
    }

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

    public Type getType() {
        return type;
    }

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

    public Retrieval getRetrieval() {
        return retrieval;
    }

    public void setRetrieval(Retrieval retrieval) {
        this.retrieval = retrieval;
    }

    public Long getStart() {
        return start;
    }

    public void setStart(Long start) {
        this.start = start;
    }

    public Long getCount() {
        return count;
    }

    public void setCount(Long count) {
        this.count = count;
    }

    public boolean isPaging() {
        return count != null || start != null;
    }

    public void setPagingInfo(long[] pagingInfo) {
        this.pagingInfo = pagingInfo;
    }

    public long[] getPagingInfo() {
        return pagingInfo;
    }

    public Integer getBinaryMajor() {
        return binaryMajor;
    }

    public void setBinaryMajor(Integer binaryMajor) {
        this.binaryMajor = binaryMajor;
    }

    public Integer getBinaryMinor() {
        return binaryMinor;
    }

    public void setBinaryMinor(Integer binaryMinor) {
        this.binaryMinor = binaryMinor;
    }

    public String getMemberName() {
        return memberName;
    }

    public void setMemberName(String memberName) {
        this.memberName = memberName;
    }

    public boolean isMemberSearchPackageOnly() {
        return memberSearchPackageOnly;
    }

    public void setMemberSearchPackageOnly(boolean memberSearchPackageOnly) {
        this.memberSearchPackageOnly = memberSearchPackageOnly;
    }

    public boolean isMemberSearchExact() {
        return memberSearchExact;
    }

    public void setMemberSearchExact(boolean memberSearchExact) {
        this.memberSearchExact = memberSearchExact;
    }

    @Override
    public String toString() {
        return "ModuleQuery[name=" + name + ",type=" + type + "]";
    }

} public enum Retrieval {
    ANY, ALL
} public enum Type {
    SRC(ArtifactContext.SRC),
    CAR(ArtifactContext.CAR),
    JAR(ArtifactContext.JAR),
    JVM(ArtifactContext.CAR, ArtifactContext.JAR),
    JS(ArtifactContext.JS),
    DART(ArtifactContext.DART),
    CODE(ArtifactContext.CAR, ArtifactContext.JAR, ArtifactContext.JS),
    CEYLON_CODE(ArtifactContext.CAR, ArtifactContext.JS),
    ALL(ArtifactContext.allSuffixes());
        
    private String[] suffixes;

    Type(String... suffixes){
        this.suffixes = suffixes;
    }

    public String[] getSuffixes() {
        return suffixes;
    }
        
    public boolean includes(String... suffs) {
        return Arrays.asList(suffixes).containsAll(Arrays.asList(suffs));
    }
} public class Overrides {
    public static class InvalidOverrideException extends IllegalArgumentException {
        private static final long serialVersionUID = 1L;
        public int line = -1;
        public int column = -1;
        public InvalidOverrideException(String message, Element element) {
            super(message);
            Object data = null;
            data = element.getUserData(LINE_NUMBER_KEY_NAME);
            this.line = data == null ? -1 : Integer.parseInt((String) data);
            data = element.getUserData(COLUMN_NUMBER_KEY_NAME);
            this.column = data == null ? -1 : Integer.parseInt((String) data);
        }
    }
    
    private Map<ArtifactContext, ArtifactOverrides> overrides = new HashMap<>();
    private Map<String, ArtifactOverrides> overridesNoVersion = new HashMap<>();
    private Set<DependencyOverride> removed = new HashSet<DependencyOverride>();

    private Map<ArtifactContext, ArtifactContext> replaced = new HashMap<>();
    private Map<String, ArtifactContext> replacedNoVersion = new HashMap<>();

    private Map<String, String> setVersions = new HashMap<>();

    public void addArtifactOverride(ArtifactOverrides ao) {
        overrides.put(ao.getOwner(), ao);
        if(ao.getOwner().getVersion() == null)
            overridesNoVersion.put(ao.getOwner().getName(), ao);
    }

    public ArtifactOverrides getArtifactOverrides(ArtifactContext mc) {
        ArtifactOverrides ao = overrides.get(mc);
        if(ao == null){
            // fall-back to overrides with no version specified
            return overridesNoVersion.get(mc.getName());
        }
        return ao;
    }

    private void addRemovedArtifact(DependencyOverride context) {
        removed.add(context);
    }

    public boolean isRemoved(ArtifactContext context){
        for(DependencyOverride ro : removed){
            if(ro.matches(context))
                return true;
        }
        return false;
    }

    public ArtifactContext getReplacement(ArtifactContext mc) {
        ArtifactContext ao = replaced.get(mc);
        if(ao == null){
            // fall-back to overrides with no version specified
            ao = replacedNoVersion.get(mc.getName());
        }
        return ao;
    }

    public ArtifactContext replace(ArtifactContext context) {
        ArtifactOverrides artifactOverrides = getArtifactOverrides(context);
        if(artifactOverrides != null && artifactOverrides.getReplace() != null){
            ArtifactContext replacingContext = artifactOverrides.getReplace().getArtifactContext();
            return replace(context, replacingContext);
        }
        ArtifactContext replacingContext = getReplacement(context);
        if(replacingContext != null){
            return replace(context, replacingContext);
        }
        return null;
    }

    private ArtifactContext replace(ArtifactContext context, ArtifactContext replacingContext) {
        // FIXME: perhaps something smarter? I don't want to use replace.getContext() as it will be missing
        // query info such as what type of artifact we're looking for 
        ArtifactContext ret = context.copy();
        ret.setName(replacingContext.getName());
        // inherit version from the artifact we replace
        if(replacingContext.getVersion() != null)
            ret.setVersion(replacingContext.getVersion());
        else
            ret.setVersion(context.getVersion());
        // even if we replace, respect the set version
        ret.setVersion(getVersionOverride(ret));
        return ret;
    }

    private void addReplacedArtifact(ArtifactContext context, ArtifactContext withContext) {
        if(context.getVersion() == null)
            replacedNoVersion.put(context.getName(), withContext);
        else
            replaced.put(context, withContext);
    }

    private void addSetArtifact(ArtifactContext context) {
        setVersions.put(context.getName(), context.getVersion());
    }
    
    public String getVersionOverride(ArtifactContext context){
        String overriddenVersion = setVersions.get(context.getName());
        if(overriddenVersion != null)
            return overriddenVersion;
        return context.getVersion();
    }
    
    public boolean isVersionOverridden(ArtifactContext context){
        return setVersions.containsKey(context.getName());
    }

    public ModuleInfo applyOverrides(String module, String version, ModuleInfo source) {
        ArtifactOverrides artifactOverrides = getArtifactOverrides(new ArtifactContext(module, version));
        Set<ModuleDependencyInfo> result = new HashSet<ModuleDependencyInfo>();
        for (ModuleDependencyInfo dep : source.getDependencies()) {
            String depName = dep.getName();
            String depVersion = dep.getVersion();
            boolean optional = dep.isOptional();
            boolean export = dep.isExport();
            ArtifactContext ctx = new ArtifactContext(depName, depVersion);
            if((artifactOverrides != null && artifactOverrides.isRemoved(ctx))
                    || isRemoved(ctx))
                continue;
            if(artifactOverrides != null && artifactOverrides.isAddedOrUpdated(ctx))
                continue;
            ArtifactContext replacement = replace(ctx);
            if(replacement != null){
                depName = replacement.getName();
                depVersion = replacement.getVersion();
                ctx = replacement;
            }
            if(isVersionOverridden(ctx))
                depVersion = getVersionOverride(ctx);
            if(artifactOverrides != null){
                if(artifactOverrides.isShareOverridden(ctx))
                    export = artifactOverrides.isShared(ctx);
                if(artifactOverrides.isOptionalOverridden(ctx))
                    optional = artifactOverrides.isOptional(ctx);
            }

            result.add(new ModuleDependencyInfo(depName, depVersion, optional, export));
        }
        String filter = source.getFilter();
        if(artifactOverrides != null){
            if(artifactOverrides.getFilter() != null)
                filter = artifactOverrides.getFilter();
            for(DependencyOverride add : artifactOverrides.getAdd()){
                result.add(new ModuleDependencyInfo(add.getArtifactContext().getName(), add.getArtifactContext().getVersion(),
                        add.isOptional(),
                        add.isShared()));
            }
        }
        return new ModuleInfo(filter, result);
    }

    public static Overrides parse(String overridesFileName) throws FileNotFoundException, Exception{
        File overridesFile = new File(overridesFileName);
        if (overridesFile.exists() == false) {
            throw new IllegalArgumentException("No such overrides file: " + overridesFile);
        }
        try(InputStream is = new FileInputStream(overridesFile)){
            return Overrides.parse(is);
        }
    }
    
    static Overrides parse(InputStream is) throws Exception {
        try {
            Overrides result = new Overrides();
            Document document = parseXml(is);
            Map<String,String> interpolation = new HashMap<>();
            List<Element> defines = getChildren(document.getDocumentElement(), "define");
            for (Element define : defines) {
                // do not interpolate while we're defining things
                String name = getRequiredAttribute(define, "name", null);
                String value = getRequiredAttribute(define, "value", null);
                interpolation.put(name, value);
            }
            
            // old name
            List<Element> artifacts = getChildren(document.getDocumentElement(), "artifact");
            parseArtifacts(artifacts, result, interpolation);
            // new name
            List<Element> modules = getChildren(document.getDocumentElement(), "module");
            parseArtifacts(modules, result, interpolation);

            List<Element> removedArtifacts = getChildren(document.getDocumentElement(), "remove");
            for (Element artifact : removedArtifacts) {
                ArtifactContext context = getArtifactContext(artifact, true, interpolation);
                DependencyOverride doo = new DependencyOverride(context, Type.REMOVE, false, false);
                result.addRemovedArtifact(doo);
            }
            List<Element> replacedArtifacts = getChildren(document.getDocumentElement(), "replace");
            for (Element artifact : replacedArtifacts) {
                ArtifactContext context = getArtifactContext(artifact, true, interpolation);
                List<Element> withs = getChildren(artifact, "with");
                for (Element with : withs) {
                    ArtifactContext withContext = getArtifactContext(with, true, interpolation);
                    result.addReplacedArtifact(context, withContext);
                }
            }
            List<Element> setArtifacts = getChildren(document.getDocumentElement(), "set");
            for (Element artifact : setArtifacts) {
                ArtifactContext context = getArtifactContext(artifact, true, interpolation);
                result.addSetArtifact(context);
            }
            return result;
        } finally {
            try {
                is.close();
            } catch (IOException ignored) {
            }
        }
    }

    private static void parseArtifacts(List<Element> artifacts, Overrides result, Map<String, String> interpolation) throws TransformerException {
        for (Element artifact : artifacts) {
            ArtifactContext mc = getArtifactContext(artifact, true, interpolation); // version is optional
            ArtifactOverrides ao = new ArtifactOverrides(mc);
            result.addArtifactOverride(ao);
            addOverrides(ao, artifact, DependencyOverride.Type.ADD, interpolation);
            addOverrides(ao, artifact, DependencyOverride.Type.REMOVE, interpolation);
            addOverrides(ao, artifact, DependencyOverride.Type.REPLACE, interpolation);
            // filter
            NodeList filterNode = artifact.getElementsByTagName("filter");
            if (filterNode != null && filterNode.getLength() > 0) {
                Node node = filterNode.item(0);
                ao.setFilter(interpolate(PathFilterParser.convertNodeToString(node), interpolation));
            }
            List<Element> shareArtifacts = getChildren(artifact, "share");
            for (Element share : shareArtifacts) {
                ArtifactContext context = getArtifactContext(share, true, interpolation);
                ao.addShareOverride(context, true);
            }
            List<Element> unshareArtifacts = getChildren(artifact, "unshare");
            for (Element unshare : unshareArtifacts) {
                ArtifactContext context = getArtifactContext(unshare, true, interpolation);
                ao.addShareOverride(context, false);
            }
            List<Element> optionalArtifacts = getChildren(artifact, "optional");
            for (Element optional : optionalArtifacts) {
                ArtifactContext context = getArtifactContext(optional, true, interpolation);
                ao.addOptionalOverride(context, true);
            }
            List<Element> requireArtifacts = getChildren(artifact, "require");
            for (Element require : requireArtifacts) {
                ArtifactContext context = getArtifactContext(require, true, interpolation);
                ao.addOptionalOverride(context, false);
            }
        }
    }

    public static String interpolate(String string, Map<String, String> interpolation) {
        if(interpolation == null || string == null || string.isEmpty())
            return string;
        int firstReplacement = string.indexOf("${");
        if(firstReplacement == -1)
            return string;
        StringBuffer strbuf = new StringBuffer(string.length());
        int start = 0;
        int[] end = new int[1];
        while(firstReplacement != -1){
            // put the start
            strbuf.append(string, start, firstReplacement);
            String part = replace(string, firstReplacement+2, end, interpolation);
            strbuf.append(part);
            // move to the end of replacement
            start = Math.min(end[0]+1, string.length());
            firstReplacement = string.indexOf("${", start);
        }
        // now put whatever remains
        strbuf.append(string, start, string.length());
        return strbuf.toString();
    }

    private static String replace(String string, int start, int[] end, Map<String, String> interpolation) {
        StringBuffer strbufName = new StringBuffer(string.length());
        // now find the end
        boolean seenDollar = false;
        for(int i=start;i<string.length();i++){
            char c = string.charAt(i);
            // new subst second char
            if(seenDollar){
                if(c == '{'){
                    String replacement = replace(string, i+1, end, interpolation);
                    strbufName.append(replacement);
                    // move to the end of the variable
                    i = end[0];
                    seenDollar = false;
                    continue;
                }
                // previous dollar was not a pattern match, add it
                strbufName.append('$');
            }
            // new subst first char
            if(c == '$'){
                seenDollar = true;
                continue;
            }else{
                seenDollar = false;
            }
            // end of subst
            if(c == '}'){
                String name = strbufName.toString();
                // we are done: now return
                end[0] = i;
                if(interpolation.containsKey((name))){
                    String value = interpolation.get(name);
                    // do not forget to interpolate values too
                    return interpolate(value, interpolation);
                }else{
                    // missing interpolation
                    return "${" + name + '}';
                }
            }else{
                strbufName.append(c);
            }
        }
        // if we've gone to the end without finding the end of "}" then let's no substitute
        end[0] = string.length();
        return "${" + strbufName.toString();
    }

    protected static ArtifactContext getArtifactContext(Element element, boolean optionalVersion, Map<String, String> interpolation) {
        String groupId = getAttribute(element, "groupId", interpolation);
        if(groupId != null){
            String artifactId = getRequiredAttribute(element, "artifactId", interpolation);
            String version = optionalVersion ? getAttribute(element, "version", interpolation) : getRequiredAttribute(element, "version", interpolation);
            String packaging = getAttribute(element, "packaging", interpolation);
            String classifier = getAttribute(element, "classifier", interpolation);
            return createMavenArtifactContext(groupId, artifactId, version, packaging, classifier);
        }else{
            String module = getRequiredAttribute(element, "module", interpolation);
            String version = optionalVersion ? getAttribute(element, "version", interpolation) : getRequiredAttribute(element, "version", interpolation);
            return new ArtifactContext(module, version);
        }
    }

    protected static void addOverrides(ArtifactOverrides ao, Element artifact, DependencyOverride.Type type, Map<String, String> interpolation) {
        List<Element> overrides = getChildren(artifact, type.name().toLowerCase());
        for (Element override : overrides) {
            ArtifactContext dep = getArtifactContext(override, type == Type.REMOVE, interpolation);
            boolean shared = getBooleanAttribute(override, "shared", interpolation);
            boolean optional = getBooleanAttribute(override, "optional", interpolation);
            DependencyOverride doo = new DependencyOverride(dep, type, shared, optional);
            ao.addOverride(doo);
        }
    }

    protected static boolean getBooleanAttribute(Element element, String name, Map<String, String> interpolation) {
        String val = getAttribute(element, name, interpolation);
        return val != null && val.toLowerCase().equals("true");
    }

    protected static String getAttribute(Element element, String name, Map<String, String> interpolation) {
        String value = interpolate(element.getAttribute(name), interpolation);
        return (value == null || value.length() == 0) ? null : value;
    }

    protected static String getRequiredAttribute(Element element, String name, Map<String, String> interpolation) {
        String value = getAttribute(element, name, interpolation);
        if (value == null) {
            throw new InvalidOverrideException(String.format("Missing '%s' attribute in element %s.", name, element), element);
        }
        return value;
    }

    final static String LINE_NUMBER_KEY_NAME = "lineNumber";
    final static String COLUMN_NUMBER_KEY_NAME = "columnNumber";

    protected static Document parseXml(InputStream inputStream) throws ParserConfigurationException, SAXException, IOException {
        final Document doc;
        SAXParser parser;
        final SAXParserFactory factory = SAXParserFactory.newInstance();
        parser = factory.newSAXParser();
        final DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
        final DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
        doc = docBuilder.newDocument();

        final Stack<Element> elementStack = new Stack<Element>();
        final StringBuilder textBuffer = new StringBuilder();
        final DefaultHandler handler = new DefaultHandler() {
            private Locator locator;

            @Override
            public void setDocumentLocator(final Locator locator) {
                this.locator = locator; // Save the locator, so that it can be used later for line tracking when traversing nodes.
            }

            @Override
            public void startElement(final String uri, final String localName, final String qName, final Attributes attributes)
                    throws SAXException {
                addTextIfNeeded();
                final Element el = doc.createElement(qName);
                for (int i = 0; i < attributes.getLength(); i++) {
                    el.setAttribute(attributes.getQName(i), attributes.getValue(i));
                }
                el.setUserData(LINE_NUMBER_KEY_NAME, String.valueOf(this.locator.getLineNumber()), null);
                el.setUserData(COLUMN_NUMBER_KEY_NAME, String.valueOf(this.locator.getColumnNumber()), null);
                elementStack.push(el);
            }

            @Override
            public void endElement(final String uri, final String localName, final String qName) {
                addTextIfNeeded();
                final Element closedEl = elementStack.pop();
                if (elementStack.isEmpty()) { // Is this the root element?
                    doc.appendChild(closedEl);
                } else {
                    final Element parentEl = elementStack.peek();
                    parentEl.appendChild(closedEl);
                }
            }

            @Override
            public void characters(final char ch[], final int start, final int length) throws SAXException {
                textBuffer.append(ch, start, length);
            }

            // Outputs text accumulated under the current node
            private void addTextIfNeeded() {
                if (textBuffer.length() > 0) {
                    final Element el = elementStack.peek();
                    final Node textNode = doc.createTextNode(textBuffer.toString());
                    el.appendChild(textNode);
                    textBuffer.delete(0, textBuffer.length());
                }
            }
        };
        parser.parse(inputStream, handler);
        doc.getDocumentElement().normalize();
        return doc;
    }

    protected static List<Element> getChildren(Element element, String tagName) {
        NodeList nodes = element.getElementsByTagName(tagName);
        List<Element> elements = new ArrayList<>(nodes.getLength());
        for (int i = 0; i < nodes.getLength(); i++) {
            elements.add((Element) nodes.item(i));
        }
        return elements;
    }

    public static ArtifactContext createMavenArtifactContext(String groupId, String artifactId, String version, 
                                                             String packaging, String classifier) {
        return new MavenArtifactContext(groupId, artifactId, version, packaging, classifier);
    }
} public interface RepositoryBuilder {
    /**
     * Build repository.
     *
     * @param token the token
     * @return repository
     * @throws Exception for any error
     */
    CmrRepository buildRepository(String token) throws Exception;
} public interface RepositoryManager {
    static final String DEFAULT_MODULE = "default";

    /**
     * Resolve dependencies for the context.
     *
     * @param name    the module name
     * @param version the module version
     * @return all dependencies, null if they cannot be found
     * @throws RepositoryException for any I/O error
     */
    File[] resolve(String name, String version) throws RepositoryException;

    /**
     * Resolve dependencies for the context.
     *
     * @param context the artifact context to resolve
     * @return all dependencies, null if they cannot be found
     * @throws RepositoryException for any I/O error
     */
    File[] resolve(ArtifactContext context) throws RepositoryException;

    /**
     * Returns an artifact as a File object, looked up by name and version
     * 
     * @param name the artifact name
     * @param version the artifact version
     * @return the File representing this artifact, if found. Null otherwise
     * 
     * @throws RepositoryException if anything went wrong
     */
    File getArtifact(String name, String version) throws RepositoryException;

    /**
     * Returns an artifact as a File object, looked up by context. This allows
     * you to specify the artifact type.
     * 
     * @param context the artifact lookup info
     * @return the File representing this artifact, if found. Null otherwise
     * 
     * @throws RepositoryException if anything went wrong
     */
    File getArtifact(ArtifactContext context) throws RepositoryException;

    /**
     * Returns an artifact as an ArtifactResult object, looked up by name and version
     * 
     * @param name the artifact name
     * @param version the artifact version
     * @return the ArtifactResult representing this artifact, if found. Null otherwise
     * 
     * @throws RepositoryException if anything went wrong
     */
    ArtifactResult getArtifactResult(String name, String version) throws RepositoryException;

    /**
     * Returns an artifact as an ArtifactResult object, looked up by context. This allows
     * you to specify the artifact type.
     * 
     * @param context the artifact lookup info
     * @return the ArtifactResult representing this artifact, if found. Null otherwise
     * 
     * @throws RepositoryException if anything went wrong
     */
    ArtifactResult getArtifactResult(ArtifactContext context) throws RepositoryException;

    /**
     * Returns ArtifactResult objects, looked up by context. This allows
     * you to specify several artifact types.
     * 
     * @param context the artifact lookup info
     * @return the list of ArtifactResult objects found. Can be empty of none of the
     * specified types was found or null if the artifact wasn't found at all
     * 
     * @throws RepositoryException if anything went wrong
     */
    List<ArtifactResult> getArtifactResults(ArtifactContext context) throws RepositoryException;

    /**
     * Publishes an artifact by name/version as an InputStream
     * 
     * @param name the artifact name
     * @param version the artifact version
     * @param content the artifact content as an InputStream
     * 
     * @throws RepositoryException if anything went wrong
     */
    void putArtifact(String name, String version, InputStream content) throws RepositoryException;

    /**
     * Publishes an artifact by name/version as a File
     * 
     * @param name the artifact name
     * @param version the artifact version
     * @param content the artifact content as a File
     * 
     * @throws RepositoryException if anything went wrong
     */
    void putArtifact(String name, String version, File content) throws RepositoryException;

    /**
     * Publishes an artifact by context as an InputStream
     * The stream is closed after this invocation.
     *
     * @param context the artifact lookup info
     * @param content the artifact content as an InputStream
     * 
     * @throws RepositoryException if anything went wrong
     */
    void putArtifact(ArtifactContext context, InputStream content) throws RepositoryException;

    /**
     * Publishes an artifact by context as a File
     * 
     * @param context the artifact lookup info
     * @param content the artifact content as a File
     * 
     * @throws RepositoryException if anything went wrong
     */
    void putArtifact(ArtifactContext context, File content) throws RepositoryException;

    /**
     * Removes an artifact by name/version
     * 
     * @param name the artifact name
     * @param version the artifact version
     * 
     * @throws RepositoryException if anything went wrong
     */
    void removeArtifact(String name, String version) throws RepositoryException;

    /**
     * Removes an artifact by context
     * 
     * @param context the artifact lookup info
     *
     * @throws RepositoryException if anything went wrong
     */
    void removeArtifact(ArtifactContext context) throws RepositoryException;

    /**
     * Returns the list of repositories for this manager.
     *
     * @return the repositories
     */
    List<CmrRepository> getRepositories();

    /**
     * Gather repositories display strings.
     *
     * @return the display strings
     */
    List<String> getRepositoriesDisplayString();
    
    /**
     * Completes a list of module names, using the given query.
     * 
     * @param query specifies the type of backend and optionally a completion match start
     * 
     * @return the list of matching module names
     */
    ModuleSearchResult completeModules(ModuleQuery query);
    
    /**
     * Completes a list of module versions, using the given query.
     * 
     * @param query specifies the module name, type of backend and optionally a completion match start
     * 
     * @return the list of matching module versions
     */
    ModuleVersionResult completeVersions(ModuleVersionQuery query);
    
    /**
     * Searches a list of module names, using the given query.
     * 
     * @param query specifies the type of backend and optionally a pattern
     * 
     * @return the list of matching module names
     */
    ModuleSearchResult searchModules(ModuleQuery query);

    /**
     * Makes sure that content cached as "unavailable" is reasessed
     */
    void refresh(boolean recurse);
    
    Overrides getOverrides();
} public class RepositoryManagerBuilder {
    private final static String PROP_CEYLON_MODULE_RESOLVER_BUILDER = "ceylon.module.resolver.builder";
    private final static String DEFAULT_DELEGATE = "com.redhat.ceylon.cmr.impl.RepositoryManagerBuilderImpl";

    private RepositoryManagerBuilder delegate;

    protected RepositoryManagerBuilder() {
    }

    public RepositoryManagerBuilder(Logger log) {
        this(log, true, 0, Proxy.NO_PROXY);
    }

    public RepositoryManagerBuilder(Logger log, boolean offline, int timeout, Proxy proxy) {
        this(log, offline, timeout, proxy, null);
    }

    public RepositoryManagerBuilder(Logger log, boolean offline, int timeout, Proxy proxy, Overrides overrides) {
        try {
            Constructor<? extends RepositoryManagerBuilder> ctor = getDelegateClass().getConstructor(Logger.class, boolean.class, int.class, Proxy.class, Overrides.class);
            delegate = ctor.newInstance(log, offline, timeout, proxy, overrides);
        } catch (java.lang.reflect.InvocationTargetException e) {
            Throwable tex = e.getTargetException();
            if (tex instanceof RuntimeException) {
                throw (RuntimeException)tex;
            } else {
                throw new IllegalArgumentException(e);
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }

    public RepositoryManagerBuilder(File mainRepository, Logger log, boolean offline, int timeout, Proxy proxy) {
        this(mainRepository, log, offline, timeout, proxy, null);
    }

    public RepositoryManagerBuilder(File mainRepository, Logger log, boolean offline, int timeout, Proxy proxy, Overrides overrides) {
        try {
            Constructor<? extends RepositoryManagerBuilder> ctor = getDelegateClass().getConstructor(File.class, Logger.class, boolean.class, int.class, Proxy.class, Overrides.class);
            delegate = ctor.newInstance(mainRepository, log, offline, timeout, proxy, overrides);
        } catch (java.lang.reflect.InvocationTargetException e) {
            Throwable tex = e.getTargetException();
            if (tex instanceof RuntimeException) {
                throw (RuntimeException)tex;
            } else {
                throw new IllegalArgumentException(tex);
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }

    @SuppressWarnings("unchecked")
    protected Class<? extends RepositoryManagerBuilder> getDelegateClass() throws Exception {
        ClassLoader classLoader = getClass().getClassLoader();
        if (classLoader == null) {
            classLoader = ClassLoader.getSystemClassLoader();
        }

        final String builderClassName = System.getProperty(PROP_CEYLON_MODULE_RESOLVER_BUILDER);
        if (builderClassName != null && builderClassName.length() > 0) {
            Class<?> clazz = classLoader.loadClass(builderClassName);
            if (RepositoryManagerBuilder.class.isAssignableFrom(clazz) == false) {
                throw new IllegalArgumentException(String.format("%s is not an instance of RepositoryManagerBuilder!", clazz.getName()));
            }
            return (Class<? extends RepositoryManagerBuilder>) clazz;
        } else {
            return (Class<? extends RepositoryManagerBuilder>) classLoader.loadClass(DEFAULT_DELEGATE);
        }
    }

    protected RepositoryManagerBuilder getDelegate() {
        if (delegate == null)
            throw new IllegalArgumentException("Missing method impl / override!");
        return delegate;
    }

    public RepositoryBuilder repositoryBuilder() {
        return getDelegate().repositoryBuilder();
    }

    public RepositoryManagerBuilder mergeStrategy(MergeStrategy strategy) {
        getDelegate().mergeStrategy(strategy);
        return this;
    }

    public RepositoryManagerBuilder contentTransformer(ContentTransformer transformer) {
        getDelegate().contentTransformer(transformer);
        return this;
    }

    public RepositoryManagerBuilder cacheContent() {
        getDelegate().cacheContent();
        return this;
    }

    public RepositoryManagerBuilder addRepository(CmrRepository external) {
        getDelegate().addRepository(external);
        return this;
    }

    public List<String> getRepositoriesDisplayString() {
        return getDelegate().getRepositoriesDisplayString();
    }

    public RepositoryManager buildRepository() {
        return getDelegate().buildRepository();
    }

    public boolean hasMavenRepository() {
        return getDelegate().hasMavenRepository();
    }

    public static Overrides parseOverrides(String overridesFileName) {
        if(overridesFileName != null){
            try {
                return Overrides.parse(overridesFileName);
            }catch(IllegalArgumentException x ){
                throw x;
            } catch (Exception e) {
                throw new IllegalStateException(e);
            }
        }
        return null;
    }
} public class DefaultRepository extends AbstractRepository {

    public DefaultRepository(OpenNode root) {
        super(root);
    }

    protected ArtifactResult getArtifactResultInternal(RepositoryManager manager, Node node) {
        return new DefaultArtifactResult(this, manager, node);
    }

    protected static class DefaultArtifactResult extends AbstractCeylonArtifactResult {

        private Node node;

        protected DefaultArtifactResult(CmrRepository repository, RepositoryManager manager, Node node) {
            super(repository, manager, ArtifactContext.fromNode(node).getName(), ArtifactContext.fromNode(node).getVersion());
            this.node = node;
        }

        protected File artifactInternal() throws RepositoryException {
            try {
                return node.getContent(File.class);
            } catch (IOException e) {
                throw new RepositoryException(e);
            }
        }
        
        @Override
        public String repositoryDisplayString() {
            String repositoryDisplayString = NodeUtils.getRepositoryDisplayString(node);
            File artifact = artifact();
            File originFile = new File(artifact.getParentFile(), artifact.getName() + RootRepositoryManager.ORIGIN);
            if (originFile.exists()) {
                try {
                    BufferedReader reader = new BufferedReader(new FileReader(originFile));
                    try {
                        String line = reader.readLine();
                        if (line != null && !line.trim().isEmpty()) {
                            repositoryDisplayString = line;
                        }
                    } finally {
                        reader.close();
                    }
                } catch(IOException e) {
                } 
            }
            return repositoryDisplayString;
        }
    }

    @Override
    public boolean isMaven() {
        return false;
    }
} public class JDKRepository extends AbstractRepository {

    public static final String JDK_VERSION = "7";

    public static final String JDK_REPOSITORY_DISPLAY_STRING = "JDK modules repository";
    
    private static final String JAVA_ORIGIN = "Java Runtime";

    private static final SortedSet<String> FixedVersionSet = new TreeSet<String>() {{
        add(JDK_VERSION);
    }};
    private static final SortedSet<String> EmptySet = new TreeSet<String>();
    private static final SortedSet<ModuleDependencyInfo> EmptyDependencySet = new TreeSet<ModuleDependencyInfo>();
    private static final SortedSet<String> FixedTypeSet = new TreeSet<String>() {{
        add(ArtifactContext.JAR);
    }};

    public static final Set<String> JDK_MODULES = new TreeSet<String>();

    static {
        for (String module : JDKUtils.getJDKModuleNames())
            JDK_MODULES.add(module);
        for (String module : JDKUtils.getOracleJDKModuleNames())
            JDK_MODULES.add(module);
    }

    public JDKRepository() {
        super(new JDKRoot());
    }

    @Override
    protected ArtifactResult getArtifactResultInternal(RepositoryManager manager, Node node) {
        return null;
    }

    public static class JDKRoot extends DefaultNode implements ContentFinderDelegate {


        public JDKRoot() {
            addService(ContentFinderDelegate.class, this);
        }

        @Override
        public String getDisplayString() {
            return JDK_REPOSITORY_DISPLAY_STRING;
        }

        @Override
        public boolean isSearchable() {
            return true;
        }

        @Override
        public void completeModules(ModuleQuery query, ModuleSearchResult result, Overrides overrides) {
            // abort if not JVM
            if (!query.getType().includes(ArtifactContext.JAR))
                return;
            if (query.getMemberName() != null && !query.isMemberSearchPackageOnly()) {
                // We don't support member searches, only package
                return;
            }
            String name = query.getName();
            if (name == null)
                name = "";
            for (String module : JDK_MODULES) {
                if (module.startsWith(name)) {
                    ModuleVersionDetails mvd = getResult(module, query);
                    if (mvd != null) {
                        result.addResult(module, mvd);
                    }
                }
            }
        }

        private String doc(String module) {
            return "JDK module " + module;
        }

        @Override
        public void completeVersions(ModuleVersionQuery query, ModuleVersionResult result, Overrides overrides) {
            // abort if not JVM
            if (!query.getType().includes(ArtifactContext.JAR))
                return;
            if (query.getName() == null || !JDK_MODULES.contains(query.getName()))
                return;
            if (query.getVersion() != null && !query.getVersion().equals(JDK_VERSION))
                return;
            final ModuleVersionDetails newVersion = result.addVersion(query.getName(), JDK_VERSION);
            newVersion.setDoc(doc(query.getName()));
            newVersion.getArtifactTypes().add(new ModuleVersionArtifact(ArtifactContext.JAR, null, null));
            newVersion.setVersion(JDK_VERSION);
            newVersion.setRemote(false);
            newVersion.setOrigin(JAVA_ORIGIN);
        }

        private boolean rightQueryType(Type type) {
            if(type == Type.ALL)
                return true;
            for(String suffix : type.getSuffixes()){
                if(suffix.equals(ArtifactContext.JAR))
                    return true;
            }
            return false;
        }

        @Override
        public void searchModules(ModuleQuery query, ModuleSearchResult result, Overrides overrides) {
            // abort if not JVM
            if (!query.getType().includes(ArtifactContext.JAR))
                return;
            if (query.getMemberName() != null && !query.isMemberSearchPackageOnly()) {
                // We don't support member searches, only package
                return;
            }
            String name = query.getName();
            if (name == null)
                name = "";
            name = name.toLowerCase();
            boolean stopSearching = false;
            int found = 0;
            for (String module : JDK_MODULES) {
                // does it match?
                if (module.contains(name)) {
                    // check if we were already done but were checking for a next results
                    if (stopSearching) {
                        // we already found enough results but were checking if there
                        // were more results to be found for paging, so record that
                        // and stop
                        result.setHasMoreResults(true);
                        return;
                    }
                    ModuleVersionDetails mvd = getResult(module, query);
                    if (mvd != null) {
                        if (query.getStart() == null || found++ >= query.getStart()) {
                            result.addResult(module, mvd);
                            // stop if we're done searching
                            if (query.getStart() != null
                                    && query.getCount() != null
                                    && found >= query.getStart() + query.getCount()) {
                                // we're done, but we want to see if there's at least one more result
                                // to be found so we can tell clients there's a next page
                                stopSearching = true;
                            }
                        }
                    }
                }
            }
        }
        
        private ModuleVersionDetails getResult(String module, ModuleQuery query) {
            ModuleVersionDetails mvd = new ModuleVersionDetails(module, JDK_VERSION);
            mvd.setDoc(doc(module));
            mvd.getArtifactTypes().add(new ModuleVersionArtifact(ArtifactContext.JAR, null, null));
            mvd.setRemote(false);
            mvd.setOrigin(JAVA_ORIGIN);
            if (query.getMemberName() != null) {
                Set<String> jdkMembers = JDKUtils.getJDKPackagesByModule(module);
                if (jdkMembers == null) {
                    jdkMembers = JDKUtils.getOracleJDKPackagesByModule(module);
                    if (jdkMembers == null) {
                        // Should not happen, but just in case
                        return null;
                    }
                }
                Set<String> matchedMembers = matchNames(jdkMembers, query, true);
                if (matchedMembers.isEmpty()) {
                    // No matching members were found
                    return null;
                }
                mvd.setMembers(matchedMembers);
            }
            return mvd;
        }
    }

    @Override
    public Node findParent(ArtifactContext context) {
        return null;
    }

    @Override
    public OpenNode createParent(ArtifactContext context) {
        return null;
    }

    @Override
    public boolean isMaven() {
        return false;
    }

} public class MavenRepositoryHelper {

    static File getMavenHome() {
        File mvnHome = new File(System.getProperty("user.home"), ".m2/repository");
        if (mvnHome.exists())
            return mvnHome;

        final String property = System.getProperty("maven.home");
        if (property != null) {
            mvnHome = new File(property, "repository");
            if (mvnHome.exists())
                return mvnHome;
        }

        throw new IllegalArgumentException("No Maven repository found!");
    }

    public static CmrRepository getMavenRepository() {
        return new MavenRepository(new MavenContentStore().createRoot());
    }

    public static CmrRepository getMavenRepository(File mvnRepository) {
        return new MavenRepository(new MavenContentStore(mvnRepository).createRoot());
    }

    public static CmrRepository getMavenRepository(String repositoryURL, Logger log, boolean offline, int timeout, Proxy proxy) {
        return new MavenRepository(new RemoteContentStore(repositoryURL, log, offline, timeout, proxy).createRoot());
    }

    public static CmrRepository getMavenRepository(StructureBuilder structureBuilder) {
        return new MavenRepository(structureBuilder.createRoot());
    }

    private static class MavenContentStore extends FileContentStore {
        private MavenContentStore() {
            this(getMavenHome());
        }

        private MavenContentStore(File root) {
            super(root);
        }

        @Override
        protected void delete(File file, Node node) {
            // cannot delete from Maven repo
        }

        @Override
        public boolean canHandleFolders() {
            return false;
        }
    }
} public class RemoteContentStore extends URLContentStore {

    public RemoteContentStore(String root, Logger log, boolean offline, int timeout, Proxy proxy) {
        super(root, log, offline, timeout, proxy);
    }

    protected SizedInputStream openSizedStream(final URL url) throws IOException {
        if (connectionAllowed()) {
            final URLConnection conn;
            if (proxy != null) {
                conn = url.openConnection(proxy);
            } else {
                conn = url.openConnection();
            }
            if (conn instanceof HttpURLConnection) {
                HttpURLConnection huc = (HttpURLConnection) conn;
                huc.setConnectTimeout(timeout);
                huc.setReadTimeout(timeout * Constants.READ_TIMEOUT_MULTIPLIER);
                addCredentials(huc);
                try{
                    InputStream stream = conn.getInputStream();
                    int code = huc.getResponseCode();
                    if (code != -1 && code != 200) {
                        log.info("Got " + code + " for url: " + url);
                        return null;
                    }
                    log.debug("Got " + code + " for url: " + url);
                    long contentLength = huc.getContentLengthLong();
                    return new SizedInputStream(stream, contentLength);
                }catch(SocketTimeoutException timeoutException){
                    SocketTimeoutException newException = new SocketTimeoutException("Timed out during connection to "+url);
                    newException.initCause(timeoutException);
                    throw newException;
                }
            }
        }
        return null;
    }

    protected boolean exists(final URL url) throws IOException {
        return head(url) != null;
    }

    public ContentHandle peekContent(Node node) {
        return urlExists(node) ? createContentHandle(null, null, null, node) : null;
    }

    public ContentHandle getContent(Node node) throws IOException {
        return createContentHandle(null, null, null, node);
    }

    public ContentHandle putContent(Node node, InputStream stream, ContentOptions options) throws IOException {
        return null; // cannot write
    }

    protected RemoteNode createNode(String label) {
        return new ImmutableRemoteNode(label);
    }

    public OpenNode create(Node parent, String child) {
        return null;
    }

    protected ContentHandle createContentHandle(Node parent, String child, String path, Node node) {
        return new RemoteContentHandle(node);
    }

    public Iterable<? extends OpenNode> find(Node parent) {
        return Collections.emptyList(); // cannot find all children
    }

    protected boolean urlExists(URL url) {
        if (url == null)
            return false;

        try {
            return exists(url);
        } catch (IOException ignored) {
            return false;
        }
    }

    @Override
    public String toString() {
        return "RemoteContentStore: " + root;
    }

    private class RemoteContentHandle implements ContentHandle {
        private final Node node;

        private RemoteContentHandle(Node node) {
            this.node = node;
        }

        public boolean hasBinaries() {
            return true; // we assume we have bins behind the url
        }

        public InputStream getBinariesAsStream() throws IOException {
            SizedInputStream ret = getBinariesAsSizedStream();
            return ret != null ? ret.inputStream : null;
        }

        public SizedInputStream getBinariesAsSizedStream() throws IOException {
            final URL url = getURL(compatiblePath(NodeUtils.getFullPath(node, SEPARATOR)));
            log.debug("Fetching resource: " + url);
            return openSizedStream(url);
        }

        public File getContentAsFile() throws IOException {
            return null;  // unsupported
        }

        public long getLastModified() throws IOException {
            final URL url = getURL(compatiblePath(NodeUtils.getFullPath(node, SEPARATOR)));
            return lastModified(url);
        }

        public long getSize() throws IOException {
            final URL url = getURL(compatiblePath(NodeUtils.getFullPath(node, SEPARATOR)));
            return size(url);
        }

        public void clean() {
        }
    }

    private static class ImmutableRemoteNode extends RemoteNode {
        private ImmutableRemoteNode(String label) {
            super(label);
        }

        @Override
        public OpenNode addContent(String label, InputStream content, ContentOptions options) throws IOException {
            return null; // cannot add content
        }

        @Override
        public <T extends Serializable> OpenNode addContent(String label, T content, ContentOptions options) throws IOException {
            return null; // cannot add content
        }
    }
} public class SimpleRepositoryManager extends AbstractNodeRepositoryManager {

    public SimpleRepositoryManager(StructureBuilder structureBuilder, Logger log) {
        super(log, null);
        if (structureBuilder == null)
            throw new IllegalArgumentException("Null structure builder!");

        setAddCacheAsRoot(true);
        setCache(new DefaultRepository(structureBuilder.createRoot()));
    }

    public SimpleRepositoryManager(CmrRepository root, Logger log) {
        this(root, log, null);
    }
    
    public SimpleRepositoryManager(CmrRepository root, Logger log, Overrides overrides) {
        super(log, overrides);
        if (root == null)
            throw new IllegalArgumentException("Null root!");

        setAddCacheAsRoot(true);
        setCache(root);
    }

    protected ArtifactResult getArtifactResult(ArtifactContext context, Node node) throws RepositoryException {
        return cache.getArtifactResult(this, node);
    }

    @Override
    public String toString() {
        return "SimpleRepositoryManager: " + getCache();
    }
} public class InMemoryContentStore implements ContentStore, StructureBuilder {

    private static final byte[] MARKER = new byte[0];
    private Map<Node, byte[]> store = new HashMap<Node, byte[]>();

    @Override
    public ContentHandle peekContent(Node node) {
        byte[] result = store.get(node);
        if (result == null || result == MARKER)
            return null;

        return new InMemoryContentHandle(result);
    }

    @Override
    public ContentHandle getContent(Node node) throws IOException {
        return peekContent(node);
    }

    @Override
    public ContentHandle putContent(Node node, InputStream stream, ContentOptions options) throws IOException {
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        IOUtils.copyStream(stream, baos, false, false);
        final byte[] bytes = baos.toByteArray();
        store.put(node, bytes);
        return new InMemoryContentHandle(bytes);
    }

    @Override
    public OpenNode createRoot() {
        return new RootNode(this, this) {
            @Override
            public boolean isRemote() {
                return true;
            }
        };
    }

    @Override
    public OpenNode create(Node parent, String child) {
        // automagically build the tree
        DefaultNode node = new DefaultNode(child) {
            @Override
            public boolean isRemote() {
                return true;
            }
        };
        store.put(node, MARKER);
        return node;
    }

    @Override
    public OpenNode find(Node parent, String child) {
        return create(parent, child);
    }

    @Override
    public Iterable<? extends OpenNode> find(Node parent) {
        return Collections.emptyList();
    }

    private static class InMemoryContentHandle implements ContentHandle {

        private byte[] bytes;
        private long lastModifed = System.currentTimeMillis();

        private InMemoryContentHandle(byte[] bytes) {
            this.bytes = bytes;
        }

        @Override
        public boolean hasBinaries() {
            return true;
        }

        @Override
        public InputStream getBinariesAsStream() throws IOException {
            return new ByteArrayInputStream(bytes);
        }

        @Override
        public SizedInputStream getBinariesAsSizedStream() throws IOException {
            return new SizedInputStream(getBinariesAsStream(), bytes.length);
        }

        @Override
        public File getContentAsFile() throws IOException {
            final File temp = File.createTempFile("ceylon-in-memory-", ".car");
            IOUtils.copyStream(getBinariesAsStream(), new FileOutputStream(temp), false, true);
            temp.deleteOnExit();
            return temp;
        }

        @Override
        public long getLastModified() throws IOException {
            return lastModifed;
        }

        @Override
        public long getSize() throws IOException {
            return bytes.length;
        }

        @Override
        public void clean() {
        }
    }

    @Override
    public String getDisplayString() {
        return "InMemoryContentStore";
    }

    @Override
    public boolean isOffline() {
        return true;
    }

    @Override
    public int getTimeout() {
        return 0;
    }

    @Override
    public boolean isHerd() {
        return false;
    }

    @Override
    public boolean canHandleFolders() {
        return true;
    }
}
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.Manifest;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import com.redhat.ceylon.cmr.api.ArtifactContext;
import com.redhat.ceylon.cmr.api.CmrRepository;
import com.redhat.ceylon.cmr.api.DebianVersionComparator;
import com.redhat.ceylon.cmr.api.MavenVersionComparator;
import com.redhat.ceylon.cmr.api.ModuleDependencyInfo;
import com.redhat.ceylon.cmr.api.ModuleQuery;
import com.redhat.ceylon.cmr.api.ModuleQuery.Retrieval;
import com.redhat.ceylon.cmr.api.ModuleQuery.Type;
import com.redhat.ceylon.cmr.api.ModuleSearchResult;
import com.redhat.ceylon.cmr.api.ModuleSearchResult.ModuleDetails;
import com.redhat.ceylon.cmr.api.ModuleVersionArtifact;
import com.redhat.ceylon.cmr.api.ModuleVersionDetails;
import com.redhat.ceylon.cmr.api.Overrides;
import com.redhat.ceylon.cmr.api.RepositoryBuilder;
import com.redhat.ceylon.cmr.api.RepositoryManager;
import com.redhat.ceylon.cmr.api.RepositoryManagerBuilder;
import com.redhat.ceylon.cmr.impl.DefaultRepository;
import com.redhat.ceylon.cmr.impl.JDKRepository;
import com.redhat.ceylon.cmr.impl.MavenRepositoryHelper;
import com.redhat.ceylon.cmr.impl.RemoteContentStore;
import com.redhat.ceylon.cmr.impl.SimpleRepositoryManager;
import com.redhat.ceylon.cmr.spi.OpenNode;
import com.redhat.ceylon.common.Constants;
import com.redhat.ceylon.model.cmr.ArtifactResult;
import com.redhat.ceylon.model.cmr.ImportType;
import com.redhat.ceylon.test.smoke.support.InMemoryContentStore;
    }

    @Test
    public void testSearchModulesFilteredByDocLicenseAndAuthorJs() throws Exception {
        ModuleDetails[] expected = new ModuleDetails[]{
        };

        testSearchResults("classic", Type.JS, expected);
        testSearchResults("domain", Type.JS, expected);
        testSearchResults("epardaud", Type.JS, expected);
    }

    @Test
    public void versionComparisonTests() {
        assertEquals(0, DebianVersionComparator.compareVersions("", ""));

        assertEquals(-1, DebianVersionComparator.compareVersions("", "a"));
        assertEquals(1, DebianVersionComparator.compareVersions("a", ""));
        assertEquals(0, DebianVersionComparator.compareVersions("a", "a"));

        assertEquals(-1, DebianVersionComparator.compareVersions("a", "b"));
        assertEquals(1, DebianVersionComparator.compareVersions("b", "a"));
        assertEquals(-1, DebianVersionComparator.compareVersions("a", "-"));
        assertEquals(1, DebianVersionComparator.compareVersions("-", "a"));

        assertEquals(-1, DebianVersionComparator.compareVersions("a", "aa"));
        assertEquals(1, DebianVersionComparator.compareVersions("aa", "a"));

        assertEquals(0, DebianVersionComparator.compareVersions("a1", "a1"));
        assertEquals(-1, DebianVersionComparator.compareVersions("a1", "a2"));
        assertEquals(0, DebianVersionComparator.compareVersions("a001", "a1"));

        assertEquals(0, DebianVersionComparator.compareVersions("1.0.2", "1.0.2"));
        assertEquals(-1, DebianVersionComparator.compareVersions("1.0.2", "1.0.2.4"));
        assertEquals(-1, DebianVersionComparator.compareVersions("1.0.2", "1.0.2b"));
        assertEquals(-1, DebianVersionComparator.compareVersions("1.0.2", "1.0.2RC"));
        assertEquals(-1, DebianVersionComparator.compareVersions("1.0.2", "1.0.2-RC"));

        assertEquals(-1, DebianVersionComparator.compareVersions("0.3", "2.2.4"));

        assertEquals(-1, DebianVersionComparator.compareVersions("1.0.2", "1.2"));
        assertEquals(-1, DebianVersionComparator.compareVersions("1.0.2", "2"));
        assertEquals(-1, DebianVersionComparator.compareVersions("1.0.2", "2.2.4"));

        assertEquals(-1, DebianVersionComparator.compareVersions("1.0", "1.0.2"));

        assertEquals(-1, DebianVersionComparator.compareVersions("0.3", "0.3.1"));
        assertEquals(1, DebianVersionComparator.compareVersions("0.3.1", "0.3"));
        assertEquals(-1, DebianVersionComparator.compareVersions("0.3.1", "0.3.2"));
        assertEquals(1, DebianVersionComparator.compareVersions("0.3.2", "0.3.1"));
    }

    @Test
    public void mavenVersionComparisonTests() {
        assertEquals(0, MavenVersionComparator.compareVersions("", ""));

        assertEquals(-1, MavenVersionComparator.compareVersions("", "c"));
        assertEquals(1, MavenVersionComparator.compareVersions("c", ""));
        assertEquals(0, MavenVersionComparator.compareVersions("c", "c"));

        assertEquals(-1, MavenVersionComparator.compareVersions("c", "d"));
        assertEquals(1, MavenVersionComparator.compareVersions("d", "c"));
        assertEquals(1, MavenVersionComparator.compareVersions("c", "-"));
        assertEquals(-1, MavenVersionComparator.compareVersions("-", "c"));

        assertEquals(-1, MavenVersionComparator.compareVersions("c", "cc"));
        assertEquals(1, MavenVersionComparator.compareVersions("cc", "c"));

        assertEquals(0, MavenVersionComparator.compareVersions("c1", "c1"));
        assertEquals(-1, MavenVersionComparator.compareVersions("c1", "c2"));
        assertEquals(0, MavenVersionComparator.compareVersions("c001", "c1"));

        assertEquals(0, MavenVersionComparator.compareVersions("1.0.2", "1.0.2"));
        assertEquals(-1, MavenVersionComparator.compareVersions("1.0.2", "1.0.2.4"));
        assertEquals(1, MavenVersionComparator.compareVersions("1.0.2", "1.0.2a"));
        assertEquals(1, MavenVersionComparator.compareVersions("1.0.2", "1.0.2alpha"));
        assertEquals(0, MavenVersionComparator.compareVersions("1.0.2a", "1.0.2alpha"));
        assertEquals(1, MavenVersionComparator.compareVersions("1.0.2", "1.0.2b"));
        assertEquals(-1, MavenVersionComparator.compareVersions("1.0.2a", "1.0.2b"));
        assertEquals(0, MavenVersionComparator.compareVersions("1.0.2b", "1.0.2beta"));
        assertEquals(-1, MavenVersionComparator.compareVersions("1.0.2b", "1.0.2m"));
        assertEquals(0, MavenVersionComparator.compareVersions("1.0.2milestone", "1.0.2m"));
        assertEquals(-1, MavenVersionComparator.compareVersions("1.0.2m", "1.0.2rc"));
        assertEquals(-1, MavenVersionComparator.compareVersions("1.0.2rc", "1.0.2snapshot"));
        assertEquals(-1, MavenVersionComparator.compareVersions("1.0.2snapshot", "1.0.2"));
        assertEquals(0, MavenVersionComparator.compareVersions("1.0.2", "1.0.2ga"));
        assertEquals(0, MavenVersionComparator.compareVersions("1.0.2", "1.0.2final"));
        assertEquals(-1, MavenVersionComparator.compareVersions("1.0.2", "1.0.2sp"));
        assertEquals(1, MavenVersionComparator.compareVersions("1.0.2", "1.0.2RC"));
        assertEquals(0, MavenVersionComparator.compareVersions("1.0.2cr", "1.0.2RC"));
        assertEquals(1, MavenVersionComparator.compareVersions("1.0.2", "1.0.2-RC"));

        assertEquals(-1, MavenVersionComparator.compareVersions("0.3", "2.2.4"));

        assertEquals(-1, MavenVersionComparator.compareVersions("1.0.2", "1.2"));
        assertEquals(-1, MavenVersionComparator.compareVersions("1.0.2", "2"));
        assertEquals(-1, MavenVersionComparator.compareVersions("1.0.2", "2.2.4"));

        assertEquals(-1, MavenVersionComparator.compareVersions("1.0", "1.0.2"));
        assertEquals(-1, MavenVersionComparator.compareVersions("1.0", "1.0-2"));

        assertEquals(-1, MavenVersionComparator.compareVersions("0.3", "0.3.1"));
        assertEquals(1, MavenVersionComparator.compareVersions("0.3.1", "0.3"));
        assertEquals(-1, MavenVersionComparator.compareVersions("0.3.1", "0.3.2"));
        assertEquals(1, MavenVersionComparator.compareVersions("0.3.2", "0.3.1"));

        assertEquals(1, MavenVersionComparator.compareVersions("18.0", "r06"));

        assertEquals(-1, MavenVersionComparator.compareVersions("1-2", "1.2"));
        assertEquals(1, MavenVersionComparator.compareVersions("1.2", "1-2"));
        assertEquals(1, MavenVersionComparator.compareVersions("1-asd", "1.asd"));
        assertEquals(-1, MavenVersionComparator.compareVersions("1.asd", "1-asd"));
        assertEquals(0, MavenVersionComparator.compareVersions("1-asd", "1-asd"));
        assertEquals(0, MavenVersionComparator.compareVersions("1-", "1."));

        assertEquals(0, MavenVersionComparator.compareVersions("", ""));
        assertEquals(0, MavenVersionComparator.compareVersions("..", "--"));
    }

    private RepositoryManager getJDKRepositoryManager() {
        return new SimpleRepositoryManager(new JDKRepository(), log);