// Copyright (c) Corporation for National Research Initiatives
// Copyright 2000 Samuele Pedroni

package org.python.core.packagecache;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;
import org.python.core.Py;
import org.python.core.PyJavaPackage;
import org.python.core.PyList;
import org.python.core.PyObject;
import org.python.core.PyStringMap;

import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.StringTokenizer;

/**
 * Abstract package manager.
 */
public abstract class PackageManager extends Object {

    /** Nominal top-level package of all (Java) packages, containing "java", "com", "org", etc.. */
    public PyJavaPackage topLevelPackage;

    public PackageManager() {
        this.topLevelPackage = new PyJavaPackage("", this);
    }

    abstract public Class findClass(String pkg, String name, String reason);

    public Class findClass(String pkg, String name) {
        return findClass(pkg, name, "java class");
    }

    public void notifyPackageImport(String pkg, String name) {}

    /**
     * Dynamically check if pkg.name exists as java pkg in the controlled hierarchy. Should be
     * overridden.
     *
     * @param pkg parent pkg name
     * @param name candidate name
     * @return true if pkg exists
     */
    public abstract boolean packageExists(String pkg, String name);

    /**
     * Reports the specified package content names. Should be overridden. Used by
     * {@link PyJavaPackage#__dir__} and {@link PyJavaPackage#fillDir}.
     *
     * @return resulting list of names (PyList of PyString)
     * @param jpkg queried package
     * @param instantiate if true then instatiate reported names in package dict
     * @param exclpkgs exclude packages (just when instantiate is false)
     */
    public abstract PyList doDir(PyJavaPackage jpkg, boolean instantiate, boolean exclpkgs);

    /**
     * Append a directory to the list of directories searched for java packages and java classes.
     *
     * @param dir A directory.
     */
    public abstract void addDirectory(java.io.File dir);

    /**
     * Append a directory to the list of directories searched for java packages and java classes.
     *
     * @param dir A directory name.
     */
    public abstract void addJarDir(String dir, boolean cache);

    /**
     * Append a jar file to the list of locations searched for java packages and java classes.
     *
     * @param jarfile A directory name.
     */
    public abstract void addJar(String jarfile, boolean cache);

    /**
     * Basic helper implementation of {@link #doDir}. It merges information from jpkg
     * {@link PyJavaPackage#clsSet} and {@link PyJavaPackage#__dict__}.
     */
    protected PyList basicDoDir(PyJavaPackage jpkg, boolean instantiate, boolean exclpkgs) {
        PyStringMap dict = jpkg.__dict__;
        PyStringMap cls = jpkg.clsSet;

        if (!instantiate) {
            PyList ret = cls.keys();
            PyList dictKeys = dict.keys();

            for (PyObject name : dictKeys.asIterable()) {
                if (!cls.has_key(name)) {
                    if (exclpkgs && dict.get(name) instanceof PyJavaPackage) {
                        continue;
                    }
                    ret.append(name);
                }
            }

            return ret;
        }

        for (PyObject pyname : cls.keys().asIterable()) {
            if (!dict.has_key(pyname)) {
                String name = pyname.toString();
                jpkg.addClass(name, Py.findClass(jpkg.__name__ + "." + name));
            }
        }

        return dict.keys();
    }

    /** Helper merging list2 into list1. Returns list1. */
    protected PyList merge(PyList list1, PyList list2) {
        for (PyObject name : list2.asIterable()) {
            list1.append(name);
        }
        return list1;
    }

    /**
     * Given the (dotted) name of a package, find the {@link PyJavaPackage} corresponding, by
     * navigating from the {@link #topLevelPackage}, successively applying
     * {@link PyObject#__findattr__(String)}. This in fact drives the creation of
     * {@link PyJavaPackage} objects since it indirectly calls
     * {@link #packageExists(String, String)}.
     *
     * @param name (dotted) package name
     * @return the package named
     */
    public PyObject lookupName(String name) {
        PyObject top = this.topLevelPackage;
        do {
            int dot = name.indexOf('.');
            String firstName = name;
            String lastName = null;
            if (dot != -1) {
                firstName = name.substring(0, dot);
                lastName = name.substring(dot + 1, name.length());
            }
            firstName = firstName.intern();
            top = top.__findattr__(firstName);
            if (top == null) {
                return null;
            }
            // ??pending: test for jpkg/jclass?
            name = lastName;
        } while (name != null);
        return top;
    }

    /**
     * Create (or ensure we have) a {@link PyJavaPackage} for the named package and add to it the
     * names of classes mentioned here. These classes are added as "place holders" only, so they
     * become members of it, without being instantiated. This method relies on
     * {@link PyJavaPackage#addPackage(java.lang.String, java.lang.String)} and
     * {@link PyJavaPackage#addPlaceholders}.
     *
     * @param name package name
     * @param classes comma or @-sign separated string
     * @param jarfile involved; can be null
     * @return created/updated package
     */
    public PyJavaPackage makeJavaPackage(String name, String classes, String jarfile) {
        PyJavaPackage p = this.topLevelPackage;
        if (name.length() != 0) {
            p = p.addPackage(name, jarfile);
        }
        p.addPlaceholders(split(classes, ",@"));
        return p;
    }

    /** ASM visitor class supporting {@link #checkAccess(InputStream)}. */
    private static class AccessVisitor extends ClassVisitor {

        private int class_access;

        public AccessVisitor() throws IOException {
            super(Opcodes.ASM7);
        }

        @Override
        public void visit(int version, int access, String name, String signature, String superName,
                String[] interfaces) {
            class_access = access;
        }

        public int getClassAccess() {
            return class_access;
        }
    }

    /**
     * Check that a given stream is a valid Java .class file, and return its access permissions as
     * an int.
     */
    static protected int checkAccess(InputStream cstream) throws IOException {
        try {
            ClassReader reader = new ClassReader(cstream);
            AccessVisitor visitor = new AccessVisitor();
            reader.accept(visitor, 0);
            return visitor.getClassAccess();
        } catch (RuntimeException e) {
            return -1;
        }
    }

    /**
     * Helper to split a textual list into a list. The semantics are basically those of
     * {@code StringTokenizer}, followed by {@code String.trim()} and duplicate removal.
     *
     * @param target compound string to split into tokens ({@code null} treated as "".
     * @param sep characters, any of which will be treated as a token separator
     * @return set of tokens trimmed of white space
     */
    protected static Set split(String target, String sep) {
        Set result = new LinkedHashSet<>();
        if (target != null) {
            StringTokenizer tok = new StringTokenizer(target, sep);
            while (tok.hasMoreTokens()) {
                String entry = tok.nextToken().trim();
                if (entry.length() > 0) {
                    result.add(entry);
                }
            }
        }
        return result;
    }

    /** Equivalent to {@code split(target, ",")}. See {@link #split(String, String)}. */
    protected static Set split(String target) {
        return split(target, ",");
    }
}

 

Ads help maintain this website.