Library.java

package Hoot.Runtime.Maps;

import java.io.*;
import java.util.*;

import Hoot.Runtime.Names.Name;
import Hoot.Runtime.Faces.Named;
import Hoot.Runtime.Names.TypeName;
import Hoot.Runtime.Behaviors.Mirror;
import Hoot.Runtime.Behaviors.Typified;
import static Hoot.Runtime.Functions.Utils.*;
import static Hoot.Runtime.Names.Operator.*;
import static Hoot.Runtime.Maps.ClassPath.*;
import static Hoot.Runtime.Names.Primitive.*;

/**
 * Maintains references to all classes and interfaces imported from external packages.
 * Packages are located relative to the system class path established by the Java environment.
 *
 * @author nik <nikboyd@sonic.net>
 * @see "Copyright 1999,2021 Nikolas S Boyd."
 * @see "Permission is granted to copy this work provided this copyright statement is retained in all copies."
 */
public class Library implements TypeName.Resolver {

    protected Library() { }
    public static final Library CurrentLib = new Library();
    public File locate(String folder) { return CurrentPath.locate(folder); }
    public boolean canLocate(Package aPackage) { return CurrentPath.canLocatePackage(aPackage); }
    public static Typified findFace(String faceName) { return CurrentLib.faceNamed(faceName); }

    // all uses, including tests
    public static void loadBasePaths(List<File> basePaths) {
        CurrentLib.configurePaths(basePaths);
        CurrentLib.loadStandardLibs();
        CurrentLib.loadNeededHootLibs();
    }

    void whileShowingOnlyDots(Runnable r) {
        try {
            clear(); CurrentPath.showOnlyDots(true); r.run();
        }
        finally {
            CurrentPath.showOnlyDots(false);
            printLine();
        }
    }

    void withoutLoadReports(Runnable r) {
        try {
            Package.reportLoads(false); r.run();
        }
        finally {
            Package.reportLoads(true);
        }
    }

    void loadStandardLibs() {
        whileShowingOnlyDots(() -> {
            mapStandardPaths();
            loadRootPackages();
            loadConfiguredPaths();
        });
    }

    void loadBasicLibs() {
        whileShowingOnlyDots(() -> {
            withoutLoadReports(() -> {
                mapStandardPaths();
                loadRootPackages();
            });
        });
    }

    public static final String[] HootLibs = { // libs in transitive dependence order
        "libs-hoot", "libs-smalltalk", "hoot-compiler", "hoot-runtime", "hoot-abstracts", };

    public static List<String> reverseHootLibs(int count) { return reverseList(listHootLibs(count)); }
    public static List<String> listHootLibs(int count) { return wrapLast(count, HootLibs); }

    public void loadAllHootLibs() { // all libs including compiler
        withoutLoadReports(() -> { clear(); CurrentPath.mapLibs(reverseHootLibs(5)); }); }

    public void loadHootSmalltalkLibs() { // only standard faces + compiler
        loadBasicLibs(); withoutLoadReports(() -> { CurrentPath.mapLibs(reverseHootLibs(4)); }); }

    public void loadHootStandardLibs() { // all libs without compiler
        withoutLoadReports(() -> {
            List<String> list = reverseHootLibs(5); list.remove(HootLibs[2]);
            clear(); CurrentPath.mapLibs(list);
        });
    }

    public void loadHootCompilerLibs() { // runtime libs with compiler
        withoutLoadReports(() -> { clear(); CurrentPath.mapLibs(reverseHootLibs(3)); }); }

    public void loadHootRuntimeLibs() { // only runtime libs
        withoutLoadReports(() -> { clear(); CurrentPath.mapLibs(reverseHootLibs(2)); }); }

    void loadNeededHootLibs() {
        // just what's needed for this library
        int limit = HootLibs.length + 1;
        for (int count = 1; count < limit; count++) {
            CurrentPath.mapLibWhenNot(listHootLibs(count));
        }
        printLine();
    }

    Map<String, Typified> faces = emptyMap(Typified.class);
    public int countFaces() { return distinctFaces().size(); }
    public boolean hasFace(String fullName) { return faces.containsKey(fullName); }
    public void removeFace(String faceName) { faces.remove(faceName); }
    public Typified faceFrom(Named reference) { return faceNamed(reference.fullName()); }
    public Set<String> distinctFaces() { return selectSet(faces.keySet(), f -> f.contains(Dot)); }
    public Typified faceNamed(String fullName) {
        if (hasNo(fullName)) return Mirror.emptyMirror();
        String faceName = Name.typeName(fullName);
        String packageName = Name.packageName(fullName);
        return packageName.isEmpty() ? faces.get(faceName) :
                packageNamed(packageName).faceNamed(faceName); }

    Map<String, Package> packages = emptyMap(Package.class);
    protected Map<String, Package> packages() { return this.packages; }
    protected Collection<Package> allPackages() { return packages().values(); }
    public int countPackages() { return packages().size(); }
    public Set<String> packageNames() { return packages().keySet(); }
    public void clear() { CurrentPath.clear(); faces.clear(); packages().clear(); whisper("cleared library"); }
    public Package packageNamed(String packageName) {
        if (packages().containsKey(packageName)) {
            return packages().get(packageName);
        }

        Package result = new Package(packageName);
        packages().put(packageName, result);
        return result;
    }

    File sourceBase;
    public String sourcePath() { return sourceBase.getAbsolutePath(); }
    public boolean whenSource(String libName) { return sourcePath().contains(libName); }

    File targetBase;
    public String targetPath() { return targetBase.getAbsolutePath(); }

    void configurePaths(List<File> basePaths) {
        sourceBase = basePaths.get(0);
        targetBase = basePaths.get(1);
    }

    public List<File> configuredPaths() { return wrap(sourceBase, targetBase); }
    public void loadConfiguredPaths() {
        List<File> list = configuredPaths(); Collections.reverse(list);
        list.forEach(path -> CurrentPath.mapPath(path));
    }

    // classes whose names are shared by both Hoot and Java
    public void removeShadowedClasses() { wrap(ShadowedClasses).forEach(c -> removeFace(c)); }
    private static final String[] ShadowedClasses = {
        "Object", "Boolean", "Character", "String",
        "Number", "Double",  "Float",  "Integer",
        "Class",  "Exception", "Error",  "Array",
    };

    public static final String[] RootPackages = { "java.lang", "java.lang.reflect", };
    public void loadRootPackages() { wrap(RootPackages).forEach(p -> packageNamed(p).loadFaces()); }
    public void mapStandardPaths() { CurrentPath.mapStandardPaths(); }

    public void loadEmptyPackages() { selectSet(allPackages(), p -> p.needsLoad()).forEach(p -> p.loadFaces()); }
    public int countPackagedFaces() { return reduce(map(allPackages(), p -> p.countFaces()), Integer::sum, 0); }
    Set<Package> includedPackages() { return selectSet(allPackages(), p -> !excludesPackage(p.name())); }

    public void addFace(Typified face) {
        String fullName = face.fullName();
        String typeName = Name.typeName(fullName);
        String packageName = Name.packageName(fullName);
        if (excludesPackage(packageName)) return;

        faces.put(fullName, face);
        faces.put(typeName, face);
        if (!packageName.isEmpty() && typeName.startsWith(packageName)) {
            typeName = typeName.substring(packageName.length() + 1);
            faces.put(typeName, face);
        }

        if (Name.isMetaNamed(fullName)) {
            faces.put(fullName.replace(Dollar, Dot), face);
            faces.put(typeName.replace(Dollar, Dot), face);
        }

        whisper("added " + fullName + " to Library");
    }

    static final String[] ExcludedLibs = {
        "org.apache", "org.slf4j", "org.abego", "org.stringtemplate",
        "st4hidden", "org.hamcrest", "org.eclipse", //"", "",
        "com.google", "org.junit", "org.codehaus", "org.antlr", "junit",
    };
    static final List<String> Exclusions = wrap(ExcludedLibs);
    boolean excludesPackage(String packageName) { return matchAny(Exclusions, ex -> packageName.startsWith(ex)); }


    public Class resolveType(Named reference) { return nullOr(f -> f.primitiveClass(), faceFrom(reference)); }
    @Override public TypeName resolveTypeNamed(Named reference) { return TypeName.fromOther(faceFrom(reference)); }

//    public boolean resolves(Named reference) {
//        if (reference.name().toString().isEmpty()) return false;
//        if (reference.name().equals(Primitive)) return true;
////        if (reference.isElementary()) return true;
////        if (reference.isGlobal()) return true;
//
//        String faceName = Name.typeName(reference.name().toString());
//        if (faceName.isEmpty()) return false;
//
//        if (Name.isMetaNamed(faceName)) {
//            faceName = Name.asMetaMember(faceName);
//        }
//
//        String packageName = Name.packageName(reference.fullName());
//        if (!packageName.isEmpty()) {
//            Typified face = faceFrom(reference);
//            if (face != null) return true;
//        }
//
//        boolean result = faces.containsKey(faceName);
//        if (result) {
////            System.out.println("Library resolved " + symbol);
//        }
//        else {
////            System.out.println("Library can't resolve " + faceName + " from " + reference.name());
//        }
//        return result;
//    }

//    public String resolveTypeName(Named reference) {
//        Class faceClass = resolveType(reference);
//        if (faceClass != null) {
//            if (Mirror.forClass(faceClass).hasMetaclass()) {
//                return MetaclassType().fullName();
//            } else {
//                return faceClass.getCanonicalName();
//            }
//        }
//
//        String symbol = Name.typeName(reference.name().toString());
//        if (faces.containsKey(symbol)) {
//            return MetaclassType().fullName();
//        }
//        return RootType().fullName();
//    }

    public static final String ChunkFileType = ".st";
    public static final String SourceFileType = ".hoot";
    static String LanguageType = SourceFileType;
    public static String languageType() { return LanguageType; }
    public static String languageType(String type) { LanguageType = type; return type; }
    public static FilenameFilter sourceFileFilter() {
        return ((File dir, String name) -> name.endsWith(languageType())); }

    public static final String TargetFileType = ".java";
    public static FilenameFilter TargetFileFilter =
        ((File dir, String name) -> name.endsWith(TargetFileType));

    static final String DiscoveryReport = "%s found %d faces";
    public void reportFacesWhen(String libName) { if (whenSource(libName)) reportFaces(); }
    public void reportFaces() { reportFacesDiscovered("discovery", reportedFaces()); }

    List<String> reportedFaces() { return sortList(copyList(collectedFaces())); }
    Set<String> collectedFaces() {
        return collectSet(fs -> includedPackages().forEach(p -> fs.addAll(p.qualifiedFaceNames()))); }

    void reportFacesDiscovered(String reportName, List<String> results) {
        report(Empty);
        report(format(DiscoveryReport, reportName, results.size()));
        if (!results.isEmpty()) {
            results.forEach(faceName -> report(faceName));
        }
    }

    static final String PackReport = "%s has %d faces";
    static final String CountReport = "packs: %d, faces: %d, packedFaces: %d";
    public void reportPackagedFaces() {
        int faceCount = countFaces();
        int packCount = countPackages();
        int pkgdCount = countPackagedFaces();
        report(format(CountReport, packCount, faceCount, pkgdCount));

        includedPackages().forEach(p -> {
            report(format(PackReport, p.name(), p.countFaces()));
        });
    }

} // Library