Package.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 Hoot.Runtime.Emissions.Item;
import Hoot.Runtime.Faces.UnitFile;
import Hoot.Runtime.Faces.Logging;
import static Hoot.Runtime.Functions.Utils.*;
import static Hoot.Runtime.Functions.Exceptional.*;
import static Hoot.Runtime.Maps.Library.*;
import static Hoot.Runtime.Maps.ClassPath.*;
import static Hoot.Runtime.Names.Keyword.*;
import static Hoot.Runtime.Names.Operator.Dot;
import static Hoot.Runtime.Names.Primitive.Blank;
import static Hoot.Runtime.Names.Primitive.Dollar;
import static Hoot.Runtime.Behaviors.HootRegistry.*;
import static Hoot.Runtime.Exceptions.ExceptionBase.*;
import static Hoot.Runtime.Names.Name.removeTail;
import org.apache.commons.lang3.SystemUtils;
import org.codehaus.plexus.util.StringUtils;

/**
 * A package of class definitions.
 *
 * @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 Package implements Named, Logging {

    public Package() { this(Empty); }
    public Package(String packageName) { super(); name = packageName; }
    public Package(String packageName, String folderPath) { this(packageName); baseFolder = folderPath; }

    String name;
    @Override public String name() { return name; }
    public void name(String packageName) { name = packageName; }

    public String pathname() { return pathFrom(name); }
    public String parentName() { return Name.packageName(name); }
    public static Package named(String packageName) { return CurrentLib.packageNamed(nameFrom(packageName)); }

    public static final String[] LiteralPackages = { "Hoot.Behaviors", "Hoot.Magnitudes", "Hoot.Collections" };
    public static final List<String> LiteralPackageNames = wrap(LiteralPackages);
    public boolean definesLiterals() { return LiteralPackageNames.contains(name()); }
    public boolean definesMagnitudes() { return LiteralPackages[1].equals(name()); }
    public boolean definesCollections() { return LiteralPackages[2].equals(name()); }
    public boolean definesBehaviors() { return (LiteralPackages[0].equals(name()) || definesSmalltalk()); }
    public boolean definesSmalltalk() { return Smalltalk.equals(name()); }
    public boolean definesRoot() { return RootPackages[0].equals(name()); }

    public static UnitFile.Factory UnitFactory;
    private UnitFile createUnit(String fullName) { return UnitFactory.createUnit(fullName, name()); }
    private Map<String, UnitFile> createPeers() {
        HashMap<String, UnitFile> results = emptyMap(UnitFile.class);
        sourceFaces().forEach(f -> results.put(f, createUnit(f)));
        results.values().forEach(f -> f.peers(results));
        return results;
    }

    public HashMap<String, UnitFile> parseSources() throws Exception {
        HashMap<String, UnitFile> results = emptyMap(UnitFile.class);
        if (UnitFactory == null) return results;

        java.io.File sourceFolder = sourceFolder();
        if (!sourceFolder.exists()) {
            error("failed to locate sources for " + name());
            return results;
        }

        results.putAll(createPeers());
        results.values().forEach(f -> f.addStandardImports());
        results.values().forEach(f -> addFace((Typified)((Item)f).facialScope()));
        results.values().forEach(f -> f.parse());

        return results;
    }

    public List<String> sourceFaces() { return listFaces(sourceFolder(), languageType(), sourceFileFilter()); }
    public List<String> targetFaces() { return listFaces(targetFolder(), TargetFileType, TargetFileFilter); }
    public List<String> listFaces(java.io.File folder, String type, FilenameFilter filter) {
        return map(wrap(folder.list(filter)), f -> removeTail(f, type)); }

    public File sourceFolder() { return new File(CurrentLib.sourcePath(), pathname()); }
    public File targetFolder() { return new File(CurrentLib.targetPath(), pathname()); }

    public File createTarget() {
        File targetFolder = targetFolder();

        if (!targetFolder.exists() && !targetFolder.mkdirs()) {
            error("can't create " + targetFolder.getAbsolutePath());
            return null;
        }

        return targetFolder;
    }

    String baseFolder = Empty;
    public File directory() {
        return (baseFolder.isEmpty()) ? CurrentPath.locate(pathname()) : new File(baseFolder + pathname()); }

    public Set<String> packagedClassNames() {
        return collectSet(results -> {
            results.addAll(CurrentPath.classesInPackage(this));
            if (baseFolder.isEmpty() && definesRoot()) {
                results.add(JavaRoot().getSimpleName());
                results.addAll(wrap(RootExceptions));
            }
        }); }

    public boolean needsLoad() { return packagedClassNames().size() > faceNames().size(); }
    public void loadFaces(Set<String> faceNames) { faceNames.forEach(faceName -> loadFace(faceName)); }

    public void loadFaces() {
        int count = faceNames().size();
        loadFaces(packagedClassNames());

        int sizeNow = faceNames().size();
        if (ReportLoads) reportLoad(name(), sizeNow);
    }

    public static boolean ReportLoads = false;
    public static void reportLoads(boolean value) { ReportLoads = value; }

    static final String CountReport = "%s loaded %d faces";
    void reportLoad(String name, int count) {
        if (ReportLoads) report(format(CountReport, name, count));
        else whisper(format(CountReport, name, count)); }

    public void loadFace(String shortName) {
        runQuietly(() -> {
            Class c = TypeName.findPrimitiveClass(qualify(shortName));
            Mirror m = Mirror.forClass(c);
            addFace(m.isTypical() ? m.reflectedType() : m);
        });
    }

    Map<String, Typified> faces = emptyMap(Typified.class);
    public int countFaces() { return faces.size(); }
    public Set<String> faceNames() { return faces.keySet(); }
    public Map<String, Typified> knownFaces() { return new HashMap<>(faces); }
    public Typified faceNamed(String faceName) { return faces.get(Name.typeName(faceName)); }
    public Set<String> qualifiedFaceNames() { return mapSet(faceNames(), f -> qualify(f)); }

    public void addFace(Typified face) {
        String packageName = face.packageName();
        String typeName = Name.typeName(face.fullName());
        if (typeName.startsWith(packageName)) {
            typeName = typeName.substring(packageName.length() + 1);
        }

        if (!CurrentLib.hasFace(typeName)) { // ??
            registerFace(face, typeName);
        }
        else { // ??
            registerFace(face, typeName);
        }
    }

    protected void registerFace(Typified face, String typeName) {
        faces.put(typeName, face);
        CurrentLib.addFace(face);
        registerMetaface(face);
        if (Name.isMetaNamed(face.fullName())) {
            packageMetaface(face);
        }
    }

    protected void registerMetaface(Typified face) {
        if (face.hasMetaface()) {
            Typified metaFace = face.$class();
            CurrentLib.addFace(metaFace);
            packageMetaface(metaFace);
        }
    }

    protected void packageMetaface(Typified metaFace) {
        if (metaFace.packageName().equals(name())) {
            addMetaface(metaFace);
        }
        else {
            Package.named(metaFace.packageName()).addMetaface(metaFace);
        }
    }

    protected void addMetaface(Typified metaFace) {
        String metaName = Name.typeName(metaFace.fullName());
        String packageName = Name.packageName(metaFace.fullName());
        if (metaName.startsWith(packageName)) {
            metaName = metaName.substring(packageName.length() + 1);
        }
        faces.put(metaName.replace(Dollar, Dot), metaFace);
        faces.put(metaName.replace(Dot, Dollar), metaFace);
    }

    public Map<String, TypeName> faceTypes() {
        HashMap<String, TypeName> results = emptyMap(TypeName.class);
        faceNames().forEach((faceName) -> {
            results.put(faceName, TypeName.fromName(qualify(faceName)));
        });
        return results;
    }

    public static final String WildCard = ".*";
    public static boolean namesAllFaces(String importName) { return importName.endsWith(WildCard); }
    public static String nameWithout(String tail, String name) { return removeTail(name, tail); }
    public static String nameFrom(File baseFolder, File packageFolder) {
        return nameFrom(packageFolder.getPath().substring(baseFolder.getPath().length())); }

    public static final String Slash = "/"; // cover Unix paths
    public static final String BackSlash = "\\"; // cover Windows too! --nik
    static String slashToDot(String packagePath) {
        return packagePath.replace(BackSlash, Blank).trim().replace(Slash, Blank).trim().replace(Blank, Dot); }
    public static String nameFrom(String packageName) { return slashToDot(nameWithout(WildCard, packageName)); }
    public static String pathFrom(String packageName) { return packageName.replace(Dot, separator()); }
    public static String separator() { return SystemUtils.IS_OS_WINDOWS ? BackSlash : Slash; }
    public static String normalPath(String folderPath) {
        return SystemUtils.IS_OS_WINDOWS ? folderPath.replace(Slash, BackSlash) : folderPath; }

    public void reportReflectively() {
        report("");
        report(fullName());
        faceNames().forEach(n -> {
            Typified type = faceNamed(n);
            if (hasSome(type)) {
                report(format(Clss, type.name()));
                reportHeritage(type.simpleHeritage(), Xtds);
                reportHeritage(type.typeHeritage(), Imps);
            }
        });
    }

    static final String Comma = ",";
    static final String Clss = "  %s";
    static final String Xtds = "       extends %s";
    static final String Imps = "    implements %s";
    static final String Tabs = "               %s";
    protected void reportHeritage(List<Typified> list, String report) {
        if (!list.isEmpty()) {
            String text = format(report, Typified.names(list).toString());
            int count = StringUtils.countMatches(text, Comma);
            if (count < 4) { report(text); return; } // exit early

            String[] parts = text.split(Comma); // take 1st three
            report(joinWith(Comma, wrap(parts[0], parts[1], parts[2])));

            text = parts[3]; // take last many
            for (int index = 4; index < parts.length; index++ ) text+=parts[index];
            report(format(Tabs, text));
        }
    }

} // Package