| /** |
| * 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 |
| * <p> |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * <p> |
| * 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 org.apache.winegrower.scanner.manifest; |
| |
| import org.apache.xbean.asm8.AnnotationVisitor; |
| import org.apache.xbean.asm8.ClassReader; |
| import org.apache.xbean.asm8.ClassVisitor; |
| import org.apache.xbean.finder.AnnotationFinder; |
| |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.function.BiFunction; |
| import java.util.function.Supplier; |
| import java.util.jar.Manifest; |
| import java.util.stream.Stream; |
| |
| import static java.util.stream.Collectors.toMap; |
| import static org.apache.xbean.asm8.ClassReader.SKIP_CODE; |
| import static org.apache.xbean.asm8.ClassReader.SKIP_DEBUG; |
| import static org.apache.xbean.asm8.ClassReader.SKIP_FRAMES; |
| import static org.apache.xbean.asm8.Opcodes.ASM8; |
| |
| public class HeaderManifestContributor implements ManifestContributor { |
| |
| @Override |
| public void contribute(final AnnotationFinder finder, final Supplier<Manifest> manifest) { |
| final ClassLoader loader = Thread.currentThread().getContextClassLoader(); |
| final List<Class<?>> headerClasses; |
| final List<Class<?>> headersClasses; |
| try { |
| final WinegrowerAnnotationFinder waf = WinegrowerAnnotationFinder.class.cast(finder); // temp, see impl |
| headerClasses = waf.findAnnotatedClasses("org.osgi.annotation.bundle.Header"); |
| headersClasses = waf.findAnnotatedClasses("org.osgi.annotation.bundle.Headers"); |
| if (headerClasses.isEmpty() && headersClasses.isEmpty()) { // reuse the finder to ensure it exists |
| return; |
| } |
| } catch (final Exception | Error e) { |
| return; |
| } |
| |
| // read it in the bytecode since reflection can't help here |
| final Map<String, String> headers = Stream.concat(headersClasses.stream(), headerClasses.stream()) |
| .flatMap(clazz -> read(loader, clazz)) |
| .collect(toMap(header -> header.name, header -> header.value, (a, b) -> { |
| throw new UnsupportedOperationException("not called normally"); |
| }, () -> new HashMap<String, String>() { |
| @Override // override to access the key which is important here |
| public String merge(final String key, final String value, |
| final BiFunction<? super String, ? super String, ? extends String> ignored) { |
| final String oldValue = get(key); |
| final String newValue = oldValue == null ? value : HeaderManifestContributor.this.mergeManifestValues(key, oldValue, value); |
| put(key, newValue); |
| return newValue; |
| } |
| })); |
| headers.forEach((k, v) -> manifest.get().getMainAttributes().putValue(k, v)); |
| } |
| |
| private Stream<KeyValue> read(final ClassLoader loader, final Class<?> clazz) { |
| try (final InputStream stream = loader.getResourceAsStream(clazz.getName().replace('.', '/') + ".class")) { |
| final ClassReader reader = new ClassReader(stream); |
| final Collection<KeyValue> headers = new ArrayList<>(); |
| final Supplier<AnnotationVisitor> newHeaderVisitor = () -> new AnnotationVisitor(ASM8) { |
| private final KeyValue header = new KeyValue(); |
| |
| @Override |
| public void visit(final String name, final Object value) { |
| switch (name) { |
| case "name": |
| header.name = String.valueOf(value); |
| break; |
| case "value": |
| header.value = String.valueOf(value) |
| .replace("${@class}", clazz.getName()); |
| break; |
| default: |
| } |
| } |
| |
| @Override |
| public void visitEnd() { |
| headers.add(header); |
| } |
| }; |
| |
| reader.accept(new ClassVisitor(ASM8) { |
| @Override |
| public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { |
| switch (descriptor) { |
| case "Lorg/osgi/annotation/bundle/Headers;": |
| return new PluralAnnotationVisitor("Lorg/osgi/annotation/bundle/Header;", newHeaderVisitor); |
| case "Lorg/osgi/annotation/bundle/Header;": |
| return newHeaderVisitor.get(); |
| default: |
| return super.visitAnnotation(descriptor, visible); |
| } |
| } |
| }, SKIP_CODE + SKIP_DEBUG + SKIP_FRAMES); |
| |
| return headers.stream(); |
| } catch (final Exception e) { |
| return Stream.empty(); |
| } |
| } |
| |
| private String mergeManifestValues(final String key, final String value1, final String value2) { |
| if ("Bundle-Activator".equals(key)) { // can't take 2 values |
| throw new IllegalArgumentException("Conflicting activators: " + value1 + ", " + value2); |
| } |
| return value1 + "," + value2; |
| } |
| |
| private static class KeyValue { |
| private String name; |
| private String value; |
| } |
| |
| private static class PluralAnnotationVisitor extends AnnotationVisitor { |
| private final String singular; |
| private final Supplier<AnnotationVisitor> visitor; |
| |
| private PluralAnnotationVisitor(final String singular, final Supplier<AnnotationVisitor> nestedVisitor) { |
| super(ASM8); |
| this.visitor = nestedVisitor; |
| this.singular = singular; |
| } |
| |
| @Override |
| public AnnotationVisitor visitArray(final String name) { |
| switch (name) { |
| case "value": |
| return new AnnotationVisitor(ASM8) { |
| @Override |
| public AnnotationVisitor visitAnnotation(final String name, final String descriptor) { |
| if (singular.equals(descriptor)) { |
| return visitor.get(); |
| } |
| return super.visitAnnotation(name, descriptor); |
| } |
| }; |
| default: |
| return super.visitArray(name); |
| } |
| } |
| } |
| } |