Package com.redhat.ceylon.tools.importjar

Source Code of com.redhat.ceylon.tools.importjar.CeylonImportJarTool

/*
* Copyright Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the authors tag. All rights reserved.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU General Public License version 2.
*
* This particular file is subject to the "Classpath" exception as provided in the
* LICENSE file that accompanied this code.
*
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE.  See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License,
* along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA  02110-1301, USA.
*/
package com.redhat.ceylon.tools.importjar;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;

import com.redhat.ceylon.cmr.api.ArtifactContext;
import com.redhat.ceylon.cmr.api.JDKUtils;
import com.redhat.ceylon.cmr.api.ModuleDependencyInfo;
import com.redhat.ceylon.cmr.api.ModuleInfo;
import com.redhat.ceylon.cmr.api.ModuleQuery;
import com.redhat.ceylon.cmr.api.ModuleSearchResult;
import com.redhat.ceylon.cmr.api.ModuleSearchResult.ModuleDetails;
import com.redhat.ceylon.cmr.api.ModuleVersionQuery;
import com.redhat.ceylon.cmr.api.RepositoryManager;
import com.redhat.ceylon.cmr.ceylon.OutputRepoUsingTool;
import com.redhat.ceylon.cmr.impl.CMRException;
import com.redhat.ceylon.cmr.impl.JarUtils;
import com.redhat.ceylon.cmr.impl.PropertiesDependencyResolver;
import com.redhat.ceylon.cmr.impl.XmlDependencyResolver;
import com.redhat.ceylon.common.Messages;
import com.redhat.ceylon.common.ModuleUtil;
import com.redhat.ceylon.common.Versions;
import com.redhat.ceylon.common.tool.Argument;
import com.redhat.ceylon.common.tool.Description;
import com.redhat.ceylon.common.tool.Option;
import com.redhat.ceylon.common.tool.OptionArgument;
import com.redhat.ceylon.common.tool.RemainingSections;
import com.redhat.ceylon.common.tool.Summary;
import com.redhat.ceylon.common.tool.ToolUsageError;
import com.redhat.ceylon.common.tools.ModuleSpec;

@Summary("Imports a jar file into a Ceylon module repository")
@Description("Imports the given `<jar-file>` using the module name and version " +
        "given by `<module>` into the repository named by the " +
        "`--out` option.\n" +
        "\n" +
        "`<module>` is a module name and version separated with a slash, for example " +
        "`com.example.foobar/1.2.0`.\n" +
        "\n" +
        "`<jar-file>` is the name of the Jar file to import.")
@RemainingSections(OutputRepoUsingTool.DOCSECTION_REPOSITORIES)
public class CeylonImportJarTool extends OutputRepoUsingTool {

    private ModuleSpec module;
    private File jarFile;
    private File descriptor;
    private boolean updateDescriptor;
    private boolean force;
    private boolean dryRun;
    private boolean showClasses;
    private boolean showSuggestions;
   
    private Set<String> jarClassNames;
    private Set<Type> checkedTypes;
    private boolean hasErrors;
    private boolean hasProblems;

    public CeylonImportJarTool() {
        super(ImportJarMessages.RESOURCE_BUNDLE);
    }
   
    CeylonImportJarTool(String moduleSpec, String out, String user, String pass, File jarFile, String verbose) {
        super(ImportJarMessages.RESOURCE_BUNDLE);
        setModuleSpec(moduleSpec);
        this.out = out;
        this.user = user;
        this.pass = pass;
        this.jarFile = jarFile;
        this.verbose = verbose;
        initialize();
    }

    @OptionArgument(argumentName="file")
    @Description("Specify a module.xml or module.properties file to be used "
            + "as the module descriptor")
    public void setDescriptor(File descriptor) {
        this.descriptor = descriptor;
    }
   
    @Argument(argumentName="module", multiplicity="1", order=0)
    public void setModuleSpec(String module) {
        setModuleSpec(ModuleSpec.parse(module,
                ModuleSpec.Option.VERSION_REQUIRED,
                ModuleSpec.Option.DEFAULT_MODULE_PROHIBITED));
    }
   
    public void setModuleSpec(ModuleSpec module) {
        this.module = module;
    }

    @Argument(argumentName="jar-file", multiplicity="1", order=1)
    public void setFile(File jarFile) {
        this.jarFile = jarFile;
    }
   
    @Option(longName="update-descriptor")
    @Description("Whenever possible will create or adjust the descriptor file with the necessary definitions.")
    public void setUpdateDescriptor(boolean updateDescriptor) {
        this.updateDescriptor = updateDescriptor;
    }
   
    @Option(longName="force")
    @Description("Skips sanity checks and forces publication of the JAR.")
    public void setForce(boolean force) {
        this.force = force;
    }
   
    @Option(longName="dry-run")
    @Description("Performs all the sanity checks but does not publish the JAR.")
    public void setDryRun(boolean dryRun) {
        this.dryRun = dryRun;
    }
   
    @Option(longName="show-classes")
    @Description("Shows all external classes that are not declared as imports instead of their packages only.")
    public void setShowClasses(boolean showClasses) {
        this.showClasses = showClasses;
    }
   
    @Option(longName="show-suggestions")
    @Description("Shows suggestions for modules based on missing package names (this can take a long time).")
    public void setShowSuggestions(boolean showSuggestions) {
        this.showSuggestions = showSuggestions;
    }
   
    @Override
    protected boolean needsSystemRepo() {
        return false;
    }

    @Override
    public void initialize() {
        setSystemProperties();
        File f = applyCwd(jarFile);
        checkReadableFile(f, "error.jarFile", true);
        if(!f.getName().toLowerCase().endsWith(".jar"))
            throw new ImportJarException("error.jarFile.notJar", new Object[]{f.toString()}, null);
       
        if (descriptor == null) {
            String baseName = f.getName().substring(0, f.getName().length() - 4);
            File desc = new File(f.getParentFile(), baseName + ".module.xml");
            if (!desc.isFile()) {
                desc = new File(f.getParentFile(), baseName + ".module.properties");
                if (desc.isFile() || updateDescriptor) {
                    descriptor = desc;
                }
            } else {
                descriptor = desc;
            }
        }
        if (descriptor != null) {
            checkReadableFile(applyCwd(descriptor), "error.descriptorFile", !updateDescriptor);
            if(!(descriptor.toString().toLowerCase().endsWith(".xml") ||
                    descriptor.toString().toLowerCase().endsWith(".properties")))
                throw new ImportJarException("error.descriptorFile.badSuffix", new Object[]{descriptor}, null);
        }
    }

    private void checkReadableFile(File f, String keyPrefix, boolean required) {
        if (f.exists()) {
            if(f.isDirectory())
                throw new ImportJarException(keyPrefix + ".isDirectory", new Object[]{f.toString()}, null);
            if(!f.canRead())
                throw new ImportJarException(keyPrefix + ".notReadable", new Object[]{f.toString()}, null);
        } else if (required) {
            throw new ImportJarException(keyPrefix + ".doesNotExist", new Object[]{f.toString()}, null);
        }
    }
   
    // Check the public API for the JAR we're importing and report any problems that are found
    private void checkPublicApi(Set<ModuleDependencyInfo> expectedDependencies) throws IOException {
        Set<String> externalClasses = gatherExternalClasses();
       
        if (descriptor != null) {
            File descriptorFile = applyCwd(descriptor);
            if (descriptorFile.exists()) {
                if (descriptor.toString().toLowerCase().endsWith(".xml")) {
                    checkModuleXml(descriptorFile, externalClasses, expectedDependencies);
                } else if(descriptor.toString().toLowerCase().endsWith(".properties")) {
                    checkModuleProperties(descriptorFile, externalClasses, expectedDependencies);
                }
            }
        }
       
        if (!externalClasses.isEmpty()) {
            if (!showClasses) {
                Set<String> externalPackages = getPackagesFromClasses(externalClasses);
                if (!externalPackages.isEmpty()) {
                    Set<String> jdkModules = gatherJdkModules(externalPackages);
                    if (!jdkModules.isEmpty()) {
                        msg("info.declare.jdk.imports").newline();
                        for (String mod : jdkModules) {
                            append("    ").append(mod).newline();
                            expectedDependencies.add(new ModuleDependencyInfo(mod, JDKUtils.jdk.version, false, true));
                        }
                        hasProblems = true;
                    }
                    if (!externalPackages.isEmpty()) {
                        msg("info.declare.module.imports").newline();
                        if (!showSuggestions) {
                            msg("info.try.suggestions").newline();
                        }
                        for (String pkg : externalPackages) {
                            append("    ").append(pkg);
                            if (showSuggestions) {
                                outputSuggestions(pkg, expectedDependencies);
                            }
                            newline();
                        }
                        hasErrors = true;
                    }
                }
                Set<String> externalDefaultClasses = getDefaultPackageClasses(externalClasses);
                if (!externalDefaultClasses.isEmpty()) {
                    msg("info.declare.class.imports").newline();
                    for (String cls : externalDefaultClasses) {
                        append("    ").append(cls).newline();
                    }
                    hasErrors = true;
                }
            } else {
                msg("info.declare.class.imports").newline();
                for (String cls : externalClasses) {
                    append("    ").append(cls).newline();
                }
                hasErrors = true;
            }
        }
    }

    // Return the set of class names for those types referenced by the
    // public API of the classes in the JAR we're importing and that are
    // not part of the JAR itself
    private Set<String> gatherExternalClasses() {
        checkedTypes = new HashSet<>();
        HashSet<String> externalClasses = new HashSet<>();
        try {
            File jar = applyCwd(jarFile);
            URLClassLoader cl = new URLClassLoader(new URL[] { jar.toURI().toURL() });
            try {
                jarClassNames = JarUtils.gatherClassnamesFromJar(jar);
                for (String className : jarClassNames) {
                    Class<?> clazz;
                    try {
                        clazz = cl.loadClass(className);
                    } catch (NoClassDefFoundError | ClassNotFoundException e) {
                        handleNotFoundErrors(externalClasses, e);
                        continue;
                    }
                    checkPublicApi(externalClasses, clazz);
                }
            } finally {
                cl.close();
            }
        } catch (IOException e) {
            throw new ImportJarException("error.jarFile.unableToAnalyze", e);
        }
        return externalClasses;
    }
   
    private void outputSuggestions(String pkg, Set<ModuleDependencyInfo> expectedDependencies) throws IOException {
        flush();
        ModuleDependencyInfo dep = null;
        Set<ModuleDetails> suggestions = findSuggestions(pkg);
        if (!suggestions.isEmpty()) {
            append(", ");
            if (suggestions.size() > 1) {
                msg("info.try.importing.multiple");
                for (ModuleDetails md : suggestions) {
                    newline();
                    String modver = md.getName() + "/" + md.getLastVersion().getVersion();
                    append("        ").append(modver);
                    dep = new ModuleDependencyInfo(md.getName(), md.getLastVersion().getVersion(), false, true);
                }
            } else {
                ModuleDetails md = suggestions.iterator().next();
                String modver = md.getName() + "/" + md.getLastVersion().getVersion();
                msg("info.try.importing", modver);
                dep = new ModuleDependencyInfo(md.getName(), md.getLastVersion().getVersion(), false, true);
            }
            if (dep != null) {
                expectedDependencies.add(dep);
                hasProblems = true;
            }
        }
    }

    private Set<ModuleDetails> findSuggestions(String pkg) {
        Set<ModuleDetails> suggestions = new TreeSet<>();
        ModuleVersionQuery query = new ModuleVersionQuery("", null, ModuleQuery.Type.JVM);
        query.setBinaryMajor(Versions.JVM_BINARY_MAJOR_VERSION);
        query.setBinaryMinor(Versions.JVM_BINARY_MINOR_VERSION);
        query.setMemberName(pkg);
        query.setMemberSearchExact(true);
        query.setMemberSearchPackageOnly(true);
        ModuleSearchResult result = getRepositoryManager().completeModules(query);
        for (ModuleDetails mvd : result.getResults()) {
            suggestions.add(mvd);
        }
        return suggestions;
    }

    // Check the public API of a class for types that are external to the JAR we're importing
    private void checkPublicApi(Set<String> externalClasses, Class<?> clazz) {
        if (clazz.getModifiers() != Modifier.PUBLIC) {
            // Not interested in any but public classes
            return;
        }
        // Make sure we get an actual class, not an array
        while (clazz.isArray()) {
            clazz = clazz.getComponentType();
        }
        try {
            checkTypes(externalClasses, clazz.getTypeParameters());
        } catch (NoClassDefFoundError | TypeNotPresentException e) {
            handleNotFoundErrors(externalClasses, e);
        }
        try {
            checkAnnotations(externalClasses, clazz.getAnnotations());
        } catch (NoClassDefFoundError | TypeNotPresentException e) {
            handleNotFoundErrors(externalClasses, e);
        }
        try {
            checkType(externalClasses, clazz.getGenericSuperclass());
        } catch (NoClassDefFoundError | TypeNotPresentException e) {
            handleNotFoundErrors(externalClasses, e);
        }
        try {
            Type[] interfaces = clazz.getGenericInterfaces();
            for (Type iface : interfaces) {
                checkType(externalClasses, iface);
            }
        } catch (NoClassDefFoundError | TypeNotPresentException e) {
            handleNotFoundErrors(externalClasses, e);
        }
        try {
            Method[] methods = clazz.getMethods();
            for (Method mth : methods) {
                if ((mth.getModifiers() & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0) {
                    checkTypes(externalClasses, mth.getTypeParameters());
                    checkAnnotations(externalClasses, mth.getAnnotations());
                    checkType(externalClasses, mth.getGenericReturnType());
                    for (Type param : mth.getGenericParameterTypes()) {
                        checkType(externalClasses, param);
                    }
                    checkAnnotations(externalClasses, mth.getParameterAnnotations());
                    for (Type ex : mth.getGenericExceptionTypes()) {
                        checkType(externalClasses, ex);
                    }
                }
            }
        } catch (NoClassDefFoundError | TypeNotPresentException e) {
            handleNotFoundErrors(externalClasses, e);
        }
        try {
            Field[] fields = clazz.getFields();
            for (Field fld : fields) {
                if ((fld.getModifiers() & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0) {
                    checkAnnotations(externalClasses, fld.getAnnotations());
                    checkType(externalClasses, fld.getGenericType());
                }
            }
        } catch (NoClassDefFoundError | TypeNotPresentException e) {
            handleNotFoundErrors(externalClasses, e);
        }
        try {
            Constructor<?>[] constructors = clazz.getConstructors();
            for (Constructor<?> cons : constructors) {
                if ((cons.getModifiers() & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0) {
                    checkTypes(externalClasses, cons.getTypeParameters());
                    checkAnnotations(externalClasses, cons.getAnnotations());
                    for (Type param : cons.getGenericParameterTypes()) {
                        checkType(externalClasses, param);
                    }
                    checkAnnotations(externalClasses, cons.getParameterAnnotations());
                    for (Type ex : cons.getGenericExceptionTypes()) {
                        checkType(externalClasses, ex);
                    }
                }
            }
        } catch (NoClassDefFoundError | TypeNotPresentException e) {
            handleNotFoundErrors(externalClasses, e);
        }
    }
   
    private void checkAnnotations(Set<String> externalClasses, Annotation[][] annotations) {
        for (Annotation[] annos : annotations) {
            checkAnnotations(externalClasses, annos);
        }
    }

    private void checkAnnotations(Set<String> externalClasses, Annotation[] annotations) {
        for (Annotation anno : annotations) {
            checkType(externalClasses, anno.annotationType());
        }
    }

    private void checkTypes(Set<String> externalClasses, Type[] types) {
        for (Type t : types) {
            checkType(externalClasses, t);
        }
    }
   
    // Check if the type is external to the JAR we're importing, if so add it to the given set
    private void checkType(Set<String> externalClasses, Type type) {
        if (type != null) {
            if (checkedTypes.contains(type)) {
                return;
            }
            checkedTypes.add(type);
            if (type instanceof Class) {
                checkClass(externalClasses, (Class<?>)type);
            } else if (type instanceof GenericArrayType) {
                checkType(externalClasses, ((GenericArrayType) type).getGenericComponentType());
            } else if (type instanceof ParameterizedType) {
                checkType(externalClasses, ((ParameterizedType) type).getOwnerType());
                for (Type t : ((ParameterizedType) type).getActualTypeArguments()) {
                    checkType(externalClasses, t);
                }
            } else if (type instanceof TypeVariable) {
                Type[] bounds;
                try {
                    bounds = ((TypeVariable<?>) type).getBounds();
                } catch (NoClassDefFoundError | TypeNotPresentException e) {
                    handleNotFoundErrors(externalClasses, e);
                    return;
                }
                for (Type b : bounds) {
                    checkType(externalClasses, b);
                }
            } else if (type instanceof WildcardType) {
                Type[] lower;
                try {
                    lower = ((WildcardType) type).getLowerBounds();
                } catch (NoClassDefFoundError | TypeNotPresentException e) {
                    handleNotFoundErrors(externalClasses, e);
                    return;
                }
                for (Type b : lower) {
                    checkType(externalClasses, b);
                }
                Type[] upper;
                try {
                    upper = ((WildcardType) type).getUpperBounds();
                } catch (NoClassDefFoundError | TypeNotPresentException e) {
                    handleNotFoundErrors(externalClasses, e);
                    return;
                }
                for (Type b : upper) {
                    checkType(externalClasses, b);
                }
            } else {
                System.err.println("Handling of type not implemented for " + type.getClass().getName());
            }
        }
    }

    // Check if the class is external to the JAR we're importing, if so add it to the given set
    private void checkClass(Set<String> externalClasses, Class<?> clazz) {
        // Make sure we get an actual class, not an array
        while (clazz.isArray()) {
            clazz = clazz.getComponentType();
        }
        if (clazz.isPrimitive()) {
            // No need to check primitives
            return;
        }
        String name = clazz.getName();
        if (jarClassNames.contains(name)) {
            // Internal to the JAR so it's okay
            return;
        }
        // FIXME Do we need to do more?
        externalClasses.add(name);
    }

    // Extract the name of the class that couldn't be loaded and add it to the given set
    private void handleNotFoundErrors(Set<String> notFound, Throwable orgth) {
        // This is very brittle because it depends on the message only containing the class name
        Throwable th = orgth;
        if (th instanceof TypeNotPresentException
                && (th.getCause() instanceof ClassNotFoundException
                        || th.getCause() instanceof NoClassDefFoundError)) {
            th = th.getCause();
        }
        String name = th.getMessage().replace('/', '.');
        if (name.startsWith("L") && name.endsWith(";")) {
            name = name.substring(1, name.length() - 1);
        }
        if (notFound.add(name)) {
            log.debug("NOT FOUND " + name);
        }
    }
   
    // Given a set of class names return the set of their package names
    // (excluding those classes that aren't in any packages)
    private Set<String> getPackagesFromClasses(Set<String> classes) {
        Set<String> packages = new TreeSet<>();
        for (String className : classes) {
            String pkg = getPackageFromClass(className);
            if (!pkg.isEmpty()) {
                packages.add(pkg);
            }
        }
        return packages;
    }

    // Given a fully qualified class name return it's package
    // (or an empty string if it's not part of any package)
    private String getPackageFromClass(String className) {
        int p = className.lastIndexOf('.');
        if (p >= 0) {
            return className.substring(0, p);
        } else {
            return "";
        }
    }
   
    // Given a set of class names returns the set of those that aren't in any package
    private Set<String> getDefaultPackageClasses(Set<String> classes) {
        Set<String> defclasses = new TreeSet<>();
        for (String className : classes) {
            int p = className.lastIndexOf('.');
            if (p < 0) {
                defclasses.add(className);
            }
        }
        return defclasses;
    }
   
    // From a list of package names we extract the ones that
    // belong to a JDK module (removing them from the original
    // list) and we return the list of JDK modules we found
    private Set<String> gatherJdkModules(Set<String> packages) {
        Set<String> jdkModules = new TreeSet<>();
        Set<String> newPackages = new HashSet<>();
        for (String pkg : packages) {
            String mod = JDKUtils.getJDKModuleNameForPackage(pkg);
            if (mod != null) {
                jdkModules.add(mod);
            } else {
                newPackages.add(pkg);
            }
        }
        packages.clear();
        packages.addAll(newPackages);
        return jdkModules;
    }

    // Publish the JAR to the specified or default output repository
    public void publish() {
        RepositoryManager outputRepository = getOutputRepositoryManager();

        ArtifactContext context = new ArtifactContext(module.getName(), module.getVersion(), ArtifactContext.JAR);
        context.setForceOperation(true);
        ArtifactContext descriptorContext = null;
        if (descriptor != null) {
            if (descriptor.toString().toLowerCase().endsWith(".xml")) {
                descriptorContext = new ArtifactContext(module.getName(), module.getVersion(), ArtifactContext.MODULE_XML);
            } else if (descriptor.toString().toLowerCase().endsWith(".properties")) {
                descriptorContext = new ArtifactContext(module.getName(), module.getVersion(), ArtifactContext.MODULE_PROPERTIES);
            }
            descriptorContext.setForceOperation(true);
        }
        try{
            File jarFile = applyCwd(this.jarFile);
            outputRepository.putArtifact(context, jarFile);
            signArtifact(context, jarFile);
           
            if (descriptorContext != null) {
                outputRepository.putArtifact(descriptorContext, applyCwd(descriptor));
            }
        }catch(CMRException x){
            throw new ImportJarException("error.failedWriteArtifact", new Object[]{context, x.getLocalizedMessage()}, x);
        }catch(Exception x){
            // FIXME: remove when the whole CMR is using CMRException
            throw new ImportJarException("error.failedWriteArtifact", new Object[]{context, x.getLocalizedMessage()}, x);
        }
    }
   
    // Check the properties descriptor file for problems and at the same time
    // remove all classes that are found within the imported modules
    // from the given set of external class names
    private void checkModuleProperties(File descriptorFile, Set<String> externalClasses, Set<ModuleDependencyInfo> expectedDependencies) throws IOException {
        try{
            ModuleInfo dependencies = PropertiesDependencyResolver.INSTANCE.resolveFromFile(descriptorFile);
            checkDependencies(dependencies, externalClasses, expectedDependencies);
        }catch(ImportJarException x){
            throw x;
        }catch(IOException x){
            throw x;
        }catch(Exception x){
            throw new ImportJarException("error.descriptorFile.invalid.properties", new Object[]{descriptorFile.getPath()}, x);
        }
    }

    // Check the XML descriptor file for problems and at the same time
    // remove all classes that are found within the imported modules
    // from the given set of external class names
    private void checkModuleXml(File descriptorFile, Set<String> externalClasses, Set<ModuleDependencyInfo> expectedDependencies) throws IOException {
        try{
            ModuleInfo dependencies = XmlDependencyResolver.INSTANCE.resolveFromFile(descriptorFile);
            checkDependencies(dependencies, externalClasses, expectedDependencies);
        }catch(ImportJarException x){
            throw x;
        }catch(IOException x){
            throw x;
        }catch(Exception x){
            throw new ImportJarException("error.descriptorFile.invalid.xml", new Object[]{descriptorFile.getPath(), x.getMessage()}, x);
        }
    }

    // Check the given dependencies for problems and at the same time
    // remove all classes that are found within the imported modules
    // from the given set of external class names
    private void checkDependencies(ModuleInfo dependencies, Set<String> externalClasses, Set<ModuleDependencyInfo> expectedDependencies) throws IOException {
        if (!dependencies.getDependencies().isEmpty()) {
            msg("info.checkingDependencies").newline();
            TreeSet<ModuleDependencyInfo> sortedDeps = new TreeSet<>(dependencies.getDependencies());
            for (ModuleDependencyInfo dep : sortedDeps) {
                String name = dep.getName();
                String version = dep.getVersion();
                // missing dep is OK, it can be fixed later, but invalid module/dependency is not OK
                if(name == null || name.isEmpty())
                    throw new ImportJarException("error.descriptorFile.invalid.module", new Object[]{name}, null);
                if(ModuleUtil.isDefaultModule(name))
                    throw new ImportJarException("error.descriptorFile.invalid.module.default");
                if(version == null || version.isEmpty())
                    throw new ImportJarException("error.descriptorFile.invalid.module.version", new Object[]{version}, null);
                append("    ").append(dep).append(" ... [");
                if (JDKUtils.isJDKModule(name) || JDKUtils.isOracleJDKModule(name)) {
                    if (removeMatchingJdkClasses(externalClasses, name)) {
                        if (dep.isExport()) {
                            msg("info.ok");
                        } else {
                            msg("error.markShared");
                            dep = new ModuleDependencyInfo(dep.getName(), dep.getVersion(), dep.isOptional(), true);
                            hasProblems = true;
                        }
                    } else {
                        if (dep.isExport()) {
                            msg("info.okButUnused");
                            dep = new ModuleDependencyInfo(dep.getName(), dep.getVersion(), dep.isOptional(), false);
                        } else {
                            msg("info.ok");
                        }
                    }
                } else {
                    ArtifactContext context = new ArtifactContext(name, dep.getVersion(), ArtifactContext.CAR, ArtifactContext.JAR);
                    File artifact = getRepositoryManager().getArtifact(context);
                    if (artifact != null && artifact.exists()) {
                        try {
                            Set<String> importedClasses = JarUtils.gatherClassnamesFromJar(artifact);
                            if (removeMatchingClasses(externalClasses, importedClasses)) {
                                if (dep.isExport()) {
                                    msg("info.ok");
                                } else {
                                    msg("error.markShared");
                                    dep = new ModuleDependencyInfo(dep.getName(), dep.getVersion(), dep.isOptional(), true);
                                    hasProblems = true;
                                }
                            } else {
                                if (dep.isExport()) {
                                    msg("info.okButUnused");
                                    dep = new ModuleDependencyInfo(dep.getName(), dep.getVersion(), dep.isOptional(), false);
                                } else {
                                    msg("info.ok");
                                }
                            }
                        } catch (IOException e) {
                            msg("error.checkFailed");
                            hasErrors = true;
                        }
                    } else {
                        msg("error.notFound");
                        hasErrors = true;
                    }
                }
                append("]").newline();
                expectedDependencies.add(dep);
            }
        }
    }

    // Remove all classes that are found within the given set of
    // imported classes from the given set of external classes
    private boolean removeMatchingClasses(Set<String> externalClasses, Set<String> importedClasses) {
        boolean matchesFound = false;
        for (String className : importedClasses) {
            matchesFound |= externalClasses.remove(className);
        }
        return matchesFound;
    }

    // Remove all classes that are part of the given JDK module
    // from the given set of external classes
    private boolean removeMatchingJdkClasses(Set<String> externalClasses, String jdkModule) {
        Set<String> toBeRemoved = new HashSet<>();
        for (String className : externalClasses) {
            String pkgName = getPackageFromClass(className);
            if (JDKUtils.isJDKPackage(jdkModule, pkgName) || JDKUtils.isOracleJDKPackage(jdkModule, pkgName)) {
                toBeRemoved.add(className);
            }
        }
        externalClasses.removeAll(toBeRemoved);
        return !toBeRemoved.isEmpty();
    }

    @Override
    public void run() throws IOException {
        Set<ModuleDependencyInfo> expectedDependencies = new TreeSet<ModuleDependencyInfo>();       
        if (!force || updateDescriptor) {
            checkPublicApi(expectedDependencies);
        }
        if (hasProblems) {
            if (updateDescriptor && descriptor != null) {
                if (!dryRun) {
                    File descriptorFile = applyCwd(descriptor);
                    if (descriptor.toString().toLowerCase().endsWith(".xml")) {
                        updateDescriptorXml(expectedDependencies, descriptorFile);
                    } else if(descriptor.toString().toLowerCase().endsWith(".properties")) {
                        updateDescriptorProperties(expectedDependencies, descriptorFile);
                    }
                }
            } else {
                hasErrors = true;
            }
        }
        if (!hasErrors || force) {
            if (!hasErrors) {
                if (force && !updateDescriptor) {
                    msg("info.forcedUpdate");
                } else {
                    msg("info.noProblems");
                }
            } else {
                msg("error.problemsFoundForced");
            }
            if (!dryRun) {
                msg("info.noProblems.publishing").newline();
                publish();
                String repoString = this.getOutputRepositoryManager().getRepositoriesDisplayString().toString();
               
                msg("info.published", this.module.toString(), repoString.substring(1, repoString.length()-1));
            }
            append(".").newline();
        } else {
            throw new ToolUsageError(Messages.msg(ImportJarMessages.RESOURCE_BUNDLE, "error.problemsFound"));
        }
    }

    private void updateDescriptorProperties(Set<ModuleDependencyInfo> expectedDependencies, File descriptorFile) throws IOException {
        Properties deps = new Properties();
        for (ModuleDependencyInfo mdi : expectedDependencies) {
            String key = mdi.getName();
            String val = mdi.getVersion();
            if (mdi.isExport()) {
                key = "+" + key;
            }
            if (mdi.isOptional()) {
                key = key + "?";
            }
            deps.setProperty(key, val);
        }
        try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(descriptorFile), "UTF-8")) {
            deps.store(out, "Generated by 'ceylon import-jar'");
        }
    }

    private void updateDescriptorXml(Set<ModuleDependencyInfo> expectedDependencies, File descriptorFile) throws IOException {
        append("Updating of XML module descriptor not yet implemented.").newline();
        hasErrors = true;
    }
}
TOP

Related Classes of com.redhat.ceylon.tools.importjar.CeylonImportJarTool

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.