com.redhat.ceylon.cmr.impl.BytecodeUtils Maven / Gradle / Ivy
/*
* Copyright 2011 Red Hat inc. and third party contributors as noted
* by the author tags.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.redhat.ceylon.cmr.impl;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.Index;
import org.jboss.jandex.Indexer;
import com.redhat.ceylon.cmr.api.AbstractDependencyResolver;
import com.redhat.ceylon.cmr.api.ArtifactContext;
import com.redhat.ceylon.cmr.api.DependencyContext;
import com.redhat.ceylon.cmr.api.ModuleDependencyInfo;
import com.redhat.ceylon.cmr.api.ModuleInfo;
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.spi.Node;
import com.redhat.ceylon.common.JVMModuleUtil;
import com.redhat.ceylon.common.ModuleUtil;
import com.redhat.ceylon.model.cmr.ArtifactResult;
import com.redhat.ceylon.model.typechecker.model.Module;
/**
* Byte hacks / utils.
*
* @author Ales Justin
*/
public final class BytecodeUtils extends AbstractDependencyResolver implements ModuleInfoReader {
public static BytecodeUtils INSTANCE = new BytecodeUtils();
private BytecodeUtils() {
}
private static final DotName MODULE_ANNOTATION = DotName.createSimple("com.redhat.ceylon.compiler.java.metadata.Module");
private static final DotName PACKAGE_ANNOTATION = DotName.createSimple("com.redhat.ceylon.compiler.java.metadata.Package");
private static final DotName CEYLON_ANNOTATION = DotName.createSimple("com.redhat.ceylon.compiler.java.metadata.Ceylon");
private static final DotName IGNORE_ANNOTATION = DotName.createSimple("com.redhat.ceylon.compiler.java.metadata.Ignore");
private static final DotName LOCAL_CONTAINER_ANNOTATION = DotName.createSimple("com.redhat.ceylon.compiler.java.metadata.LocalContainer");
@Override
public ModuleInfo resolve(DependencyContext context, Overrides overrides) {
if (context.ignoreInner()) {
return null;
}
final ArtifactResult result = context.result();
return readModuleInformation(result.name(), result.artifact(), overrides);
}
@Override
public ModuleInfo resolveFromFile(File file, String name, String version, Overrides overrides) {
throw new UnsupportedOperationException("Operation not supported for .car files");
}
@Override
public ModuleInfo resolveFromInputStream(InputStream stream, String name, String version, Overrides overrides) {
throw new UnsupportedOperationException("Operation not supported for .car files");
}
@Override
public Node descriptor(Node artifact) {
return null; // artifact is a descriptor
}
/**
* Read module info from bytecode.
*
* @param moduleName the module name
* @param jarFile the module jar file
* @return module info list
*/
private static ModuleInfo readModuleInformation(final String moduleName, final File jarFile, Overrides overrides) {
Index index = readModuleIndex(moduleName, jarFile, false);
final AnnotationInstance ai = getAnnotation(index, moduleName, MODULE_ANNOTATION);
if (ai == null)
return null;
final AnnotationValue version = ai.value("version");
if(version == null)
return null;
final AnnotationValue dependencies = ai.value("dependencies");
if (dependencies == null)
return new ModuleInfo(null, Collections.emptySet());
final Set infos = new LinkedHashSet();
final AnnotationInstance[] imports = dependencies.asNestedArray();
if (imports != null){
for (AnnotationInstance im : imports) {
final String name = asString(im, "name");
final ModuleDependencyInfo mi = new ModuleDependencyInfo(
name,
asString(im, "version"),
asBoolean(im, "optional"),
asBoolean(im, "export"));
infos.add(mi);
}
}
ModuleInfo ret = new ModuleInfo(null, infos);
if(overrides != null)
ret = overrides.applyOverrides(moduleName, version.asString(), ret);
return ret;
}
private static Index readModuleIndex(String moduleName, final File jarFile, boolean everything) {
try {
try(JarFile jar = new JarFile(jarFile)){
Indexer indexer = new Indexer();
// FIXME: try reading the index directly rather than iterate
if(!everything){
// default module has no module descriptor
if(Module.DEFAULT_MODULE_NAME.equals(moduleName))
return indexer.complete();
String modulePath = getModulePath(moduleName);
String name1 = modulePath+"/$module_.class";
JarEntry entry = jar.getJarEntry(name1);
if(entry == null){
String name2 = modulePath+"/module_.class";
entry = jar.getJarEntry(name2);
}
if(entry != null){
try(InputStream stream = jar.getInputStream(entry)){
indexer.index(stream);
}
}
return indexer.complete();
}
Enumeration entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName().toLowerCase();
if(everything && name.endsWith(".class")){
try(InputStream stream = jar.getInputStream(entry)){
indexer.index(stream);
}
}
}
return indexer.complete();
}
} catch (IOException e) {
throw new RuntimeException("Failed to read index for module " + jarFile.getPath(), e);
}
}
private static String getModulePath(String moduleName) {
String quotedModuleName = JVMModuleUtil.quoteJavaKeywords(moduleName);
return quotedModuleName.replace('.', '/');
}
private static ClassInfo getModuleInfo(final Index index, final String moduleName) {
// we need to escape any java keyword from the package list
String quotedModuleName = JVMModuleUtil.quoteJavaKeywords(moduleName);
DotName moduleClassName = DotName.createSimple(quotedModuleName + ".$module_");
ClassInfo ret = index.getClassByName(moduleClassName);
if(ret == null){
// read previous module descriptor name
moduleClassName = DotName.createSimple(quotedModuleName + ".module_");
ret = index.getClassByName(moduleClassName);
}
return ret;
}
@Override
public int[] getBinaryVersions(String moduleName, String moduleVersion, File moduleArchive) {
Index index = readModuleIndex(moduleName, moduleArchive, false);
final AnnotationInstance ceylonAnnotation = getAnnotation(index, moduleName, CEYLON_ANNOTATION);
if (ceylonAnnotation == null)
return null;
AnnotationValue majorAnnotation = ceylonAnnotation.value("major");
AnnotationValue minorAnnotation = ceylonAnnotation.value("minor");
int major = majorAnnotation != null ? majorAnnotation.asInt() : 0;
int minor = minorAnnotation != null ? minorAnnotation.asInt() : 0;
return new int[]{major, minor};
}
@Override
public ModuleVersionDetails readModuleInfo(String moduleName, String moduleVersion, File moduleArchive, boolean includeMembers, Overrides overrides) {
Index index = readModuleIndex(moduleName, moduleArchive, true);
final AnnotationInstance moduleAnnotation = getAnnotation(index, moduleName, MODULE_ANNOTATION);
if (moduleAnnotation == null)
return null;
AnnotationValue doc = moduleAnnotation.value("doc");
AnnotationValue license = moduleAnnotation.value("license");
AnnotationValue by = moduleAnnotation.value("by");
AnnotationValue dependencies = moduleAnnotation.value("dependencies");
String type = ArtifactContext.getSuffixFromFilename(moduleArchive.getName());
final AnnotationInstance ceylonAnnotation = getAnnotation(index, moduleName, CEYLON_ANNOTATION);
if (ceylonAnnotation == null)
return null;
AnnotationValue majorVer = ceylonAnnotation.value("major");
AnnotationValue minorVer = ceylonAnnotation.value("minor");
ModuleVersionDetails mvd = new ModuleVersionDetails(moduleName, getVersionFromFilename(moduleName, moduleArchive.getName()));
mvd.setDoc(doc != null ? doc.asString() : null);
mvd.setLicense(license != null ? license.asString() : null);
if (by != null) {
mvd.getAuthors().addAll(Arrays.asList(by.asStringArray()));
}
mvd.getDependencies().addAll(getDependencies(dependencies, moduleName, mvd.getVersion(), overrides));
ModuleVersionArtifact mva = new ModuleVersionArtifact(type, majorVer != null ? majorVer.asInt() : 0, minorVer != null ? minorVer.asInt() : 0);
mvd.getArtifactTypes().add(mva);
if (includeMembers) {
mvd.setMembers(getMembers(index));
}
return mvd;
}
private Set getMembers(Index index) {
HashSet members = new HashSet<>();
for (ClassInfo cls : index.getKnownClasses()) {
if (shouldAddMember(cls)) {
members.add(classNameToDeclName(cls.name().toString()));
}
}
return members;
}
private boolean shouldAddMember(ClassInfo cls) {
// ignore what we must ignore
if (getClassAnnotation(cls, IGNORE_ANNOTATION) != null) {
return false;
}
// ignore module and package descriptors
if (getClassAnnotation(cls, MODULE_ANNOTATION) != null || getClassAnnotation(cls, PACKAGE_ANNOTATION) != null) {
return false;
}
// ignore local types
if (getClassAnnotation(cls, LOCAL_CONTAINER_ANNOTATION) != null) {
return false;
}
return true;
}
private AnnotationInstance getClassAnnotation(ClassInfo cls, DotName annoName) {
List annos = cls.annotations().get(annoName);
if (annos != null) {
// Just return the first one we can find on the class itself
for (AnnotationInstance anno : annos) {
if (anno.target() == cls) {
return anno;
}
}
}
return null;
}
// Returns a fully qualified declaration name making sure that
// package name and member name are separated by "::"
private static String classNameToDeclName(String clsName) {
int lastDot = clsName.lastIndexOf('.');
String packageName = lastDot != -1 ? clsName.substring(0, lastDot) : "";
String simpleName = lastDot != -1 ? clsName.substring(lastDot+1) : clsName;
// ceylon names have mangling for interface members that we pull to toplevel
simpleName = simpleName.replace("$impl$", ".");
// turn any dollar sep into a dot
simpleName = simpleName.replace('$', '.');
// remove any dollar prefixes and trailing underscores
return unquotedDeclName(packageName, simpleName);
}
// Given a fully qualified package and member name returns the full and
// unquoted declaration name stripped of all special symbols like '$' and '_'
private static String unquotedDeclName(String pkg, String member) {
if (pkg != null && !pkg.isEmpty()) {
return unquoteName(pkg, false) + "::" + unquoteName(member, true);
} else {
return unquoteName(member, true);
}
}
// Given a name consisting of parts separated by dots returns the unquoted
// version stripped of all special symbols like '$' and '_'
private static String unquoteName(String s, boolean stripTrailingUnderscore) {
if (s != null) {
String[] parts = JVMModuleUtil.unquoteJavaKeywords(s.split("\\."));
String name = parts[parts.length - 1];
if (stripTrailingUnderscore && !name.isEmpty() && Character.isLowerCase(name.charAt(0)) && name.charAt(name.length()-1) == '_') {
name = name.substring(0, name.length()-1);
}
parts[parts.length - 1] = name;
s = JVMModuleUtil.join(".", parts);
}
return s;
}
private static String getVersionFromFilename(String moduleName, String name) {
if (!ModuleUtil.isDefaultModule(moduleName)) {
String type = ArtifactContext.getSuffixFromFilename(name);
return name.substring(moduleName.length() + 1, name.length() - type.length());
} else {
return "";
}
}
private static Set getDependencies(AnnotationValue dependencies, String module, String version, Overrides overrides) {
AnnotationInstance[] deps = dependencies.asNestedArray();
Set result = new HashSet(deps.length);
for (AnnotationInstance dep : deps) {
AnnotationValue depName = dep.value("name");
AnnotationValue depVersion = dep.value("version");
AnnotationValue export = dep.value("export");
AnnotationValue optional = dep.value("optional");
result.add(new ModuleDependencyInfo(depName.asString(), depVersion.asString(),
(optional!= null) && optional.asBoolean(),
(export != null) && export.asBoolean()));
}
if(overrides != null)
return overrides.applyOverrides(module, version, new ModuleInfo(null, result)).getDependencies();
return result;
}
public boolean matchesModuleInfo(String moduleName, String moduleVersion, File moduleArchive, String query, Overrides overrides) {
Index index = readModuleIndex(moduleName, moduleArchive, false);
final AnnotationInstance moduleAnnotation = getAnnotation(index, moduleName, MODULE_ANNOTATION);
if (moduleAnnotation == null)
return false;
AnnotationValue version = moduleAnnotation.value("version");
if (version == null)
return false;
AnnotationValue doc = moduleAnnotation.value("doc");
if (doc != null && matches(doc.asString(), query))
return true;
AnnotationValue license = moduleAnnotation.value("license");
if (license != null && matches(license.asString(), query))
return true;
AnnotationValue by = moduleAnnotation.value("by");
if (by != null) {
for (String author : by.asStringArray()) {
if (matches(author, query))
return true;
}
}
AnnotationValue dependencies = moduleAnnotation.value("dependencies");
if (dependencies != null) {
for (ModuleDependencyInfo dep : getDependencies(dependencies, moduleName, version.asString(), overrides)) {
if (matches(dep.getModuleName(), query))
return true;
}
}
return false;
}
private static boolean matches(String string, String query) {
return string.toLowerCase().contains(query);
}
private static String asString(AnnotationInstance ai, String name) {
final AnnotationValue av = ai.value(name);
if (av == null)
throw new IllegalArgumentException("Missing required annotation attribute: " + name);
return av.asString();
}
private static boolean asBoolean(AnnotationInstance ai, String name) {
final AnnotationValue av = ai.value(name);
return (av != null) && av.asBoolean();
}
private static AnnotationInstance getAnnotation(Index index, String moduleName, DotName annotationName) {
final ClassInfo moduleClass = getModuleInfo(index, moduleName);
if (moduleClass == null)
return null;
List annotations = moduleClass.annotations().get(annotationName);
if (annotations == null || annotations.isEmpty())
return null;
return annotations.get(0);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy