From aa44951faf04e2cddfc537bbc9f6a18d76c76ec1 Mon Sep 17 00:00:00 2001 From: Jan Srnicek Date: Fri, 21 Jul 2017 15:10:46 +0200 Subject: HC2VPP-180 - Doc coverage generator TODO - links to specific vpp api section(now points just to section with apis) TODO - links to specific java binding code(now points to class thats doing binding) TODO - operational coverage(ASM does not support lambda processing) TODO - generate coverage adoc links Change-Id: I44c85012da3bd2e7cdd41930753e5aae6955cd7b Signed-off-by: Jan Srnicek Signed-off-by: Marek Gradzki --- .../io/fd/hc2vpp/docs/core/ClassPathTypeIndex.java | 74 +++++++++ .../hc2vpp/docs/core/CollectingWriterBuilder.java | 153 +++++++++++++++++ .../io/fd/hc2vpp/docs/core/CoverageGenerator.java | 184 +++++++++++++++++++++ .../io/fd/hc2vpp/docs/core/CoverageScanner.java | 74 +++++++++ .../java/io/fd/hc2vpp/docs/core/LinkGenerator.java | 33 ++++ .../docs/core/MethodDelegatingClassVisitor.java | 54 ++++++ .../docs/core/MethodPluginCoverageVisitor.java | 90 ++++++++++ .../io/fd/hc2vpp/docs/core/ModelLinkIndex.java | 71 ++++++++ .../io/fd/hc2vpp/docs/core/ModelTypeIndex.java | 88 ++++++++++ .../fd/hc2vpp/docs/core/PluginMethodReference.java | 56 +++++++ .../java/io/fd/hc2vpp/docs/core/VppApiUtils.java | 43 +++++ .../java/io/fd/hc2vpp/docs/core/YangModelKey.java | 73 ++++++++ .../io/fd/hc2vpp/docs/core/YangTypeLinkIndex.java | 56 +++++++ 13 files changed, 1049 insertions(+) create mode 100644 vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/ClassPathTypeIndex.java create mode 100644 vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/CollectingWriterBuilder.java create mode 100644 vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/CoverageGenerator.java create mode 100644 vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/CoverageScanner.java create mode 100644 vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/LinkGenerator.java create mode 100644 vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/MethodDelegatingClassVisitor.java create mode 100644 vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/MethodPluginCoverageVisitor.java create mode 100644 vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/ModelLinkIndex.java create mode 100644 vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/ModelTypeIndex.java create mode 100644 vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/PluginMethodReference.java create mode 100644 vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/VppApiUtils.java create mode 100644 vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/YangModelKey.java create mode 100644 vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/YangTypeLinkIndex.java (limited to 'vpp-integration/api-docs/core/src') diff --git a/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/ClassPathTypeIndex.java b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/ClassPathTypeIndex.java new file mode 100644 index 000000000..e204d6321 --- /dev/null +++ b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/ClassPathTypeIndex.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * + * 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 io.fd.hc2vpp.docs.core; + +import static java.lang.String.format; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; +import java.util.stream.Collectors; + + +/** + * Index of java classes to relative absolute paths within repository. Used to generate Git links for binding classes of + * VPP apis + */ +public class ClassPathTypeIndex implements LinkGenerator { + + private static final String JAVA_SOURCE_FOLDER = "src/main/java"; + private static final int JAVA_SOURCE_FOLDER_NAME_LENGTH = JAVA_SOURCE_FOLDER.length() + 1; + + /** + *
  • key - fully qualified class name value
  • value - path within codebase/repository
  • + */ + private final Map index; + + public ClassPathTypeIndex(final String projectRoot, final String version) { + index = buildIndex(projectRoot, version); + } + + /** + *
  • input format - LOCAL_ROOT/hc2vpp/module/src/main/java/fully/qualified/class/name/Class.java
  • output + * format - fully.qualified.class.name.Class
  • + */ + private static String key(String raw) { + return raw.substring(raw.indexOf(JAVA_SOURCE_FOLDER) + JAVA_SOURCE_FOLDER_NAME_LENGTH) + .replace("/", ".") + .replace(".java", ""); + } + + public String linkForClass(final String clazz) { + return index.get(clazz.replace("/", ".")); + } + + private Map buildIndex(final String projectRoot, final String version) { + try { + return Files.walk(Paths.get(projectRoot)) + .filter(path -> path.toString().contains("src/main/java")) + .filter(path -> path.toString().endsWith(".java")) + .map(Path::toString) + .map(s -> s.replace(projectRoot, "")) + .distinct() + .collect(Collectors.toMap(ClassPathTypeIndex::key, o -> generateLink(o, version))); + } catch (IOException e) { + throw new IllegalStateException(format("%s not found", projectRoot), e); + } + } +} diff --git a/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/CollectingWriterBuilder.java b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/CollectingWriterBuilder.java new file mode 100644 index 000000000..b8fcc8b03 --- /dev/null +++ b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/CollectingWriterBuilder.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * + * 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 io.fd.hc2vpp.docs.core; + + +import com.google.common.collect.ImmutableSet; +import io.fd.honeycomb.translate.ModifiableSubtreeManagerRegistryBuilder; +import io.fd.honeycomb.translate.write.Writer; +import io.fd.honeycomb.translate.write.registry.ModifiableWriterRegistryBuilder; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Implementations of builder that collects handlers as they are bind + */ +public class CollectingWriterBuilder implements ModifiableWriterRegistryBuilder { + + private final List> singleNodeHandlers; + private final List multiNodeWriteHandlers; + + public CollectingWriterBuilder() { + singleNodeHandlers = new LinkedList<>(); + multiNodeWriteHandlers = new LinkedList<>(); + } + + @Override + public ModifiableSubtreeManagerRegistryBuilder> add( + @Nonnull Writer handler) { + singleNodeHandlers.add(handler); + return this; + } + + @Override + public ModifiableSubtreeManagerRegistryBuilder> subtreeAdd( + @Nonnull Set> handledChildren, @Nonnull Writer handler) { + multiNodeWriteHandlers.add(new MultiNodeWriteHandler(handler, handledChildren)); + return this; + } + + @Override + public ModifiableSubtreeManagerRegistryBuilder> addBefore( + @Nonnull Writer handler, @Nonnull InstanceIdentifier relatedType) { + multiNodeWriteHandlers.add(new MultiNodeWriteHandler(handler, Collections.singleton(relatedType))); + return this; + } + + @Override + public ModifiableSubtreeManagerRegistryBuilder> addBefore( + @Nonnull Writer handler, @Nonnull Collection> relatedTypes) { + singleNodeHandlers.add(handler); + return this; + } + + @Override + public ModifiableSubtreeManagerRegistryBuilder> subtreeAddBefore( + @Nonnull Set> handledChildren, @Nonnull Writer handler, + @Nonnull InstanceIdentifier relatedType) { + multiNodeWriteHandlers.add(new MultiNodeWriteHandler(handler, handledChildren)); + return null; + } + + @Override + public ModifiableSubtreeManagerRegistryBuilder> subtreeAddBefore( + @Nonnull Set> handledChildren, @Nonnull Writer handler, + @Nonnull Collection> relatedTypes) { + multiNodeWriteHandlers.add(new MultiNodeWriteHandler(handler, handledChildren)); + return this; + } + + @Override + public ModifiableSubtreeManagerRegistryBuilder> addAfter( + @Nonnull Writer handler, @Nonnull InstanceIdentifier relatedType) { + singleNodeHandlers.add(handler); + return this; + } + + @Override + public ModifiableSubtreeManagerRegistryBuilder> addAfter( + @Nonnull Writer handler, @Nonnull Collection> relatedTypes) { + singleNodeHandlers.add(handler); + return this; + } + + @Override + public ModifiableSubtreeManagerRegistryBuilder> subtreeAddAfter( + @Nonnull Set> handledChildren, @Nonnull Writer handler, + @Nonnull InstanceIdentifier relatedType) { + multiNodeWriteHandlers.add(new MultiNodeWriteHandler(handler, Collections.singleton(relatedType))); + return this; + } + + @Override + public ModifiableSubtreeManagerRegistryBuilder> subtreeAddAfter( + @Nonnull Set> handledChildren, @Nonnull Writer handler, + @Nonnull Collection> relatedTypes) { + multiNodeWriteHandlers.add(new MultiNodeWriteHandler(handler, handledChildren)); + return this; + } + + public List> getSingleNodeHandlers() { + return singleNodeHandlers; + } + + public List getMultiNodeWriteHandlers() { + return multiNodeWriteHandlers; + } + + public static class MultiNodeWriteHandler { + private final Writer writer; + private final Set handledChildren; + + + public MultiNodeWriteHandler(Writer writer, Set> handledChildren) { + this.writer = writer; + this.handledChildren = ImmutableSet.builder() + .add(writer.getManagedDataObjectType().getTargetType().getName()) + .addAll(handledChildren.stream() + .map(InstanceIdentifier::getTargetType) + .map(Class::getName) + .collect(Collectors.toSet())) + .build(); + } + + public Writer getWriter() { + return writer; + } + + public Set getHandledChildren() { + return handledChildren; + } + } +} diff --git a/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/CoverageGenerator.java b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/CoverageGenerator.java new file mode 100644 index 000000000..4b6ab776c --- /dev/null +++ b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/CoverageGenerator.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * + * 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 io.fd.hc2vpp.docs.core; + + +import static io.fd.hc2vpp.docs.api.Operation.CrudOperation.DELETE; +import static io.fd.hc2vpp.docs.api.Operation.CrudOperation.UPDATE; +import static io.fd.hc2vpp.docs.api.Operation.CrudOperation.WRITE; +import static java.lang.String.format; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Module; +import com.google.inject.TypeLiteral; +import io.fd.hc2vpp.docs.api.CoverageUnit; +import io.fd.hc2vpp.docs.api.JavaApiMessage; +import io.fd.hc2vpp.docs.api.Operation; +import io.fd.hc2vpp.docs.api.PluginCoverage; +import io.fd.hc2vpp.docs.api.YangType; +import io.fd.honeycomb.translate.write.WriterFactory; +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.reflections.ReflectionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CoverageGenerator implements VppApiUtils { + + private static final Logger LOG = LoggerFactory.getLogger(CoverageGenerator.class); + + private final CollectingWriterBuilder writerBuilder; + + public CoverageGenerator() { + writerBuilder = new CollectingWriterBuilder(); + } + + public PluginCoverage generateConfigCoverage(final Class pluginClass, + final String version, + final List scannedModules, + final YangTypeLinkIndex yangTypeIndex, + final ClassPathTypeIndex classPathIndex) { + LOG.info("Generating config coverage for plugin {}", pluginClass); + getInjectedWriterFactories(scannedModules).forEach(writerFactory -> writerFactory.init(writerBuilder)); + + LOG.info("Processing single node handlers"); + final Set singleNodeCoverageUnits = writerBuilder.getSingleNodeHandlers().stream() + .flatMap(writer -> { + // extracts customizer class from handler + final Class customizerClass = getCustomizerClass(writer); + + // scans within write method + final Set writeReferences = + new CoverageScanner(customizerClass, WRITE, pluginClass).scan(); + + // scans within update method + final Set updateReferences = + new CoverageScanner(customizerClass, UPDATE, pluginClass).scan(); + + // scans within delete method + final Set deleteReferences = + new CoverageScanner(customizerClass, DELETE, pluginClass).scan(); + + return Stream.of(writeReferences.stream(), updateReferences.stream(), deleteReferences.stream()) + .flatMap(pluginMethodReferences -> pluginMethodReferences) + .map(reference -> { + final CoverageUnit.CoverageUnitBuilder builder = new CoverageUnit.CoverageUnitBuilder(); + + // binds vpp api name and generateLink bind with version + builder.setVppApi(fromJvppApi(version, reference)); + + //binds java api reference + builder.setJavaApi(new JavaApiMessage(reference.getName())); + + //binds Yang types with links from pre-build index + // TODO - use deserialized yii + final String typeName = writer.getManagedDataObjectType().getTargetType().getTypeName(); + builder.setYangTypes(Collections.singletonList(new YangType( + typeName, + yangTypeIndex.getLinkForType(typeName)))); + + final List supportedOperations = new LinkedList<>(); + + final String callerClassLink = classPathIndex.linkForClass(reference.getCaller()); + if (writeReferences.contains(reference)) { + supportedOperations.add(new Operation(callerClassLink, WRITE)); + } + + if (updateReferences.contains(reference)) { + supportedOperations.add(new Operation(callerClassLink, UPDATE)); + } + + if (deleteReferences.contains(reference)) { + supportedOperations.add(new Operation(callerClassLink, DELETE)); + } + return builder.setSupportedOperations(supportedOperations).build(); + }); + }).collect(Collectors.toSet()); + + LOG.info("Processing multi node handlers"); + final Set multiNodeCoverageUnits = writerBuilder.getMultiNodeWriteHandlers().stream() + .flatMap(handler -> { + final Class customizerClass = getCustomizerClass(handler.getWriter()); + final Set writeReferences = + new CoverageScanner(customizerClass, WRITE, pluginClass).scan(); + + final Set updateReferences = + new CoverageScanner(customizerClass, UPDATE, pluginClass).scan(); + + final Set deleteReferences = + new CoverageScanner(customizerClass, DELETE, pluginClass).scan(); + + return Stream.of(writeReferences.stream(), updateReferences.stream(), deleteReferences.stream()) + .flatMap(pluginMethodReferenceStream -> pluginMethodReferenceStream) + .map(reference -> { + final CoverageUnit.CoverageUnitBuilder builder = new CoverageUnit.CoverageUnitBuilder(); + builder.setVppApi(fromJvppApi(version, reference)); + builder.setJavaApi(new JavaApiMessage(reference.getName())); + + builder.setYangTypes(handler.getHandledChildren().stream() + .map(type -> new YangType(type, yangTypeIndex.getLinkForType(type))) + .collect(Collectors.toList())); + + final String callerClassLink = classPathIndex.linkForClass(reference.getCaller()); + final List supportedOperations = new LinkedList<>(); + if (writeReferences.contains(reference)) { + supportedOperations.add(new Operation(callerClassLink, WRITE)); + } + + if (updateReferences.contains(reference)) { + supportedOperations.add(new Operation(callerClassLink, UPDATE)); + } + + if (deleteReferences.contains(reference)) { + supportedOperations.add(new Operation(callerClassLink, DELETE)); + } + return builder.setSupportedOperations(supportedOperations).build(); + }); + }).collect(Collectors.toSet()); + + return new PluginCoverage(pluginClass.getSimpleName(), + Stream.of(singleNodeCoverageUnits.stream(), multiNodeCoverageUnits.stream()) + .flatMap(coverageUnitStream -> coverageUnitStream) + .collect(Collectors.toSet()), true); + } + + private static Class getCustomizerClass(final Object handler) { + try { + final Set customizerFields = + ReflectionUtils.getAllFields(handler.getClass(), field -> "customizer".equals(field.getName())); + final Field customizerField = customizerFields.iterator().next(); + customizerField.setAccessible(true); + return customizerField.get(handler).getClass(); + } catch (IllegalAccessException e) { + throw new IllegalStateException(format("Unable to get customizer from %s ", handler), e); + } + } + + private static Set getInjectedWriterFactories(final List scannedModules) { + Injector injector = Guice.createInjector(scannedModules); + TypeLiteral> writerFactoryType = new TypeLiteral>() { + }; + return injector.getInstance(Key.get(writerFactoryType)); + } +} diff --git a/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/CoverageScanner.java b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/CoverageScanner.java new file mode 100644 index 000000000..c1c67d77b --- /dev/null +++ b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/CoverageScanner.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * + * 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 io.fd.hc2vpp.docs.core; + + +import static java.lang.String.format; + +import io.fd.hc2vpp.docs.api.Operation; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import org.objectweb.asm.ClassReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Scans provided class for reference under specified crud method + */ +public class CoverageScanner { + + private static final Logger LOG = LoggerFactory.getLogger(CoverageScanner.class); + + private final Class classToScan; + private final Operation.CrudOperation crudOperation; + private final Class referenceClass; + + public CoverageScanner(final Class classToScan, + final Operation.CrudOperation crudOperation, + final Class referenceClass) { + this.classToScan = classToScan; + this.crudOperation = crudOperation; + this.referenceClass = referenceClass; + } + + static ClassReader loadClass(String className) { + try (InputStream classStream = + CoverageScanner.class.getClassLoader().getResourceAsStream(className + ".class")) { + return new ClassReader(classStream); + } catch (IOException e) { + throw new IllegalStateException(format("Unable to load bytecode for class %s", className), e); + } + } + + public Set scan() { + LOG.debug("Scanning class {}", classToScan.getName()); + final ClassReader classReader = loadClass(byteCodeStyleReference(classToScan.getName())); + final Set foundReferences = Collections.synchronizedSet(new HashSet<>()); + classReader.accept(new MethodDelegatingClassVisitor(byteCodeStyleReference(classToScan.getName()), + crudOperation.getMethodReference(), byteCodeStyleReference(referenceClass.getPackage().getName()), + foundReferences, null), ClassReader.SKIP_DEBUG); + return foundReferences; + } + + // converts java style reference to bytecode-style name(with slashes instead of dots) + private static String byteCodeStyleReference(final String javaStyleName) { + return javaStyleName.replace(".", "/"); + } +} diff --git a/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/LinkGenerator.java b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/LinkGenerator.java new file mode 100644 index 000000000..3226639b1 --- /dev/null +++ b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/LinkGenerator.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * + * 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 io.fd.hc2vpp.docs.core; + +public interface LinkGenerator { + + static String resolveBranch(final String version) { + if (version.contains("SNAPSHOT")) { + return "master"; + } else { + return "stable%2F" + version.replace(".", ""); + } + } + + default String generateLink(final String raw, final String version) { + //https://git.fd.io/hc2vpp/tree/interface-role/api/src/main/yang/interface-role@2017-06-15.yang?h=stable%2F1707 + return "https://git.fd.io/hc2vpp/tree" + raw + "?h=" + resolveBranch(version); + } +} diff --git a/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/MethodDelegatingClassVisitor.java b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/MethodDelegatingClassVisitor.java new file mode 100644 index 000000000..39a06e626 --- /dev/null +++ b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/MethodDelegatingClassVisitor.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * + * 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 io.fd.hc2vpp.docs.core; + + +import java.util.Set; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class MethodDelegatingClassVisitor extends ClassVisitor { + + private final String currentClass; + private final String methodName; + private final String reference; + private final Set foundReferences; + private final Set allreadyProcessedLocalMethods; + + public MethodDelegatingClassVisitor(String currentClass, + String methodName, + String reference, + Set foundReferences, + Set allreadyProcessedLocalMethods) { + super(Opcodes.ASM5); + this.currentClass = currentClass; + this.methodName = methodName; + this.reference = reference; + this.foundReferences = foundReferences; + this.allreadyProcessedLocalMethods = allreadyProcessedLocalMethods; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if (name.equals(methodName)) { + return new MethodPluginCoverageVisitor(currentClass, foundReferences, reference, + allreadyProcessedLocalMethods); + } + return super.visitMethod(access, name, desc, signature, exceptions); + } +} diff --git a/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/MethodPluginCoverageVisitor.java b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/MethodPluginCoverageVisitor.java new file mode 100644 index 000000000..fe15f5e79 --- /dev/null +++ b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/MethodPluginCoverageVisitor.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * + * 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 io.fd.hc2vpp.docs.core; + +import static java.lang.String.format; + +import java.util.HashSet; +import java.util.Set; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MethodPluginCoverageVisitor extends MethodVisitor { + + private static final Logger LOG = LoggerFactory.getLogger(MethodPluginCoverageVisitor.class); + + private final String currentClass; + private final Set foundReferences; + private final String reference; + private final Set allreadyProcessedLocal; + + public MethodPluginCoverageVisitor(String currentClass, Set foundReferences, + String reference, + Set allreadyProcessedLocal) { + super(Opcodes.ASM5); + this.currentClass = currentClass; + this.foundReferences = foundReferences; + this.reference = reference; + // if nonnull then reuse + this.allreadyProcessedLocal = allreadyProcessedLocal == null + ? new HashSet<>() + : allreadyProcessedLocal; + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean isInterface) { + final String normalizedOwner = owner.replace(";", "").replace("[L", ""); + if (normalizedOwner.contains(reference)) { + // reference was found directly in method code + foundReferences.add(new PluginMethodReference(currentClass, owner, name)); + } else { + if (normalizedOwner.contains("io/fd")) { + // filter just our method to reduce scope + if (!normalizedOwner.equals(currentClass)) { + LOG.debug("Processing non-current {}.{}()", normalizedOwner, name); + // method call is from different class than currently processed one + ClassReader classReader = CoverageScanner.loadClass(normalizedOwner); + classReader.accept(new MethodDelegatingClassVisitor(normalizedOwner, name, reference, + foundReferences, + allreadyProcessedLocal), ClassReader.SKIP_DEBUG); + } else { + LOG.debug("Processing current {}.{}()", normalizedOwner, name); + // other methods in same class that are used in visited method + String fullyQualifiedMethodName = fullyQualifiedMethodName(normalizedOwner, name, desc); + if (allreadyProcessedLocal.contains(fullyQualifiedMethodName)) { + //skip already processed local methods to prevent stack overflow + return; + } + allreadyProcessedLocal.add(fullyQualifiedMethodName); + + ClassReader classReader = CoverageScanner.loadClass(normalizedOwner); + classReader.accept(new MethodDelegatingClassVisitor(normalizedOwner, name, reference, + foundReferences, + allreadyProcessedLocal), ClassReader.SKIP_DEBUG); + } + } + } + super.visitMethodInsn(opcode, owner, name, desc, isInterface); + } + + private String fullyQualifiedMethodName(String owner, String name, String desc) { + return format("%s_%s_%s", owner, name, desc); + } +} diff --git a/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/ModelLinkIndex.java b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/ModelLinkIndex.java new file mode 100644 index 000000000..c1b299826 --- /dev/null +++ b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/ModelLinkIndex.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * + * 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 io.fd.hc2vpp.docs.core; + +import static java.lang.String.format; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * Index from module file name to git generateLink + */ +class ModelLinkIndex implements LinkGenerator { + + private final Map modelLinkIndex; + + /** + * @param projectRoot for ex.: /home/jsrnicek/Projects/hc2vpp + * @param version for ex.: 17.07 to get generateLink for correct branch + */ + ModelLinkIndex(final String projectRoot, final String version) { + modelLinkIndex = buildIndex(projectRoot, version); + } + + private static String key(String raw) { + return raw.substring(raw.lastIndexOf("/")) + .replace("/", "") + .replace(".yang", ""); + } + + String linkForModel(final String model, final String revision) { + // TODO - figure out how to get revision for model in src,to use YangModelKey + // if not local model,add generateLink to ietf datatracker + return Optional.ofNullable(modelLinkIndex.get(model + "@" + revision)) + .orElse(Optional.ofNullable(modelLinkIndex.get(model)) + .orElse("https://datatracker.ietf.org/")); + } + + private Map buildIndex(final String projectRoot, final String version) { + try { + return Files.walk(Paths.get(projectRoot)) + .filter(path -> path.toString().contains("src/main/yang")) + .filter(path -> path.toString().endsWith(".yang")) + .map(Path::toString) + .map(s -> s.replace(projectRoot, "")) + .distinct() + .collect(Collectors.toMap(ModelLinkIndex::key, o -> generateLink(o, version))); + } catch (IOException e) { + throw new IllegalStateException(format("%s not found", projectRoot), e); + } + } +} diff --git a/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/ModelTypeIndex.java b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/ModelTypeIndex.java new file mode 100644 index 000000000..9fd5976b7 --- /dev/null +++ b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/ModelTypeIndex.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * + * 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 io.fd.hc2vpp.docs.core; + +import static java.util.stream.Collectors.toMap; + +import com.google.common.io.Resources; +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import org.opendaylight.yangtools.yang.binding.YangModelBindingProvider; +import org.opendaylight.yangtools.yang.binding.YangModuleInfo; + +/** + * Maps namespaces to models + */ +class ModelTypeIndex { + + private final Map namespaceToModuleIndex; + + ModelTypeIndex() throws IOException { + namespaceToModuleIndex = collectAllModules(this.getClass().getClassLoader()) + .stream() + .collect(toMap(YangModelKey::new, YangModuleInfo::getName)); + } + + private static YangModelBindingProvider getModuleBindingProviderInstance(final Class aClass) { + try { + return (YangModelBindingProvider) aClass.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new IllegalStateException(e); + } + } + + private static Class loadClass(final ClassLoader classLoader, final String name) { + try { + return classLoader.loadClass(name); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e); + + } + } + + private static List loadResource(final URL url) { + try { + return Resources.readLines(url, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + String namespaceToModule(final String namespace, + final String revision) { + return namespaceToModuleIndex.get(new YangModelKey(namespace, revision)); + } + + private Set collectAllModules(final ClassLoader classLoader) throws IOException { + return Collections.list(classLoader.getResources("META-INF/services/" + + YangModelBindingProvider.class.getName())) + .stream() + .map(ModelTypeIndex::loadResource) + .flatMap(Collection::stream) + .map(name -> loadClass(classLoader, name)) + .map(ModelTypeIndex::getModuleBindingProviderInstance) + .map(YangModelBindingProvider::getModuleInfo) + .collect(Collectors.toSet()); + } +} diff --git a/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/PluginMethodReference.java b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/PluginMethodReference.java new file mode 100644 index 000000000..5a98639ae --- /dev/null +++ b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/PluginMethodReference.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * + * 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 io.fd.hc2vpp.docs.core; + +/** + * Represent found reference of plugin method + */ +public class PluginMethodReference { + + /** + * Name of the class that uses such reference + */ + private final String caller; + + /** + * Class of the reference + */ + private final String owner; + + /** + * Name of the reference + */ + private final String name; + + public PluginMethodReference(final String caller, final String owner, final String name) { + this.caller = caller; + this.owner = owner; + this.name = name; + } + + public String getCaller() { + return caller; + } + + public String getOwner() { + return owner; + } + + public String getName() { + return name; + } +} diff --git a/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/VppApiUtils.java b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/VppApiUtils.java new file mode 100644 index 000000000..f21cc2660 --- /dev/null +++ b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/VppApiUtils.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * + * 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 io.fd.hc2vpp.docs.core; + +import com.google.common.base.CaseFormat; +import io.fd.hc2vpp.docs.api.VppApiMessage; + + +public interface VppApiUtils { + + static String vppApiFromJavaApi(final String jvppApi) { + return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, jvppApi); + } + + static String generateVppApiDocLink(final String version, final String vppApi) { + //https://docs.fd.io/vpp/17.07/d9/d1d/structvl__api__create__subif__t.html + // links are using double underscore + //final String doubleUnderscoreApiName = vppApi.replace("_", "__"); + //return format("https://docs.fd.io/vpp/%s/d9/d1d/structvl__api__%s__t.html", version, doubleUnderscoreApiName); + + // FIXME - generateLink has dynamic part that can be resolved from api name + return "https://docs.fd.io/vpp/17.07/annotated.html"; + } + + default VppApiMessage fromJvppApi(final String version, final PluginMethodReference jvppApi) { + final String vppApi = vppApiFromJavaApi(jvppApi.getName()); + return new VppApiMessage(vppApi, generateVppApiDocLink(version, vppApi)); + } +} diff --git a/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/YangModelKey.java b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/YangModelKey.java new file mode 100644 index 000000000..2bc5bc5da --- /dev/null +++ b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/YangModelKey.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * + * 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 io.fd.hc2vpp.docs.core;import org.opendaylight.yangtools.yang.binding.YangModuleInfo; + +final class YangModelKey { + private final String namespace; + private final String revision; + + YangModelKey(final YangModuleInfo moduleInfo) { + this.namespace = moduleInfo.getNamespace(); + this.revision = moduleInfo.getRevision(); + } + + YangModelKey(final String namespace, final String revision) { + this.namespace = namespace; + this.revision = revision; + } + + + public String getNamespace() { + return namespace; + } + + public String getRevision() { + return revision; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final YangModelKey that = (YangModelKey) o; + + if (namespace != null + ? !namespace.equals(that.namespace) + : that.namespace != null) { + return false; + } + return revision != null + ? revision.equals(that.revision) + : that.revision == null; + } + + @Override + public int hashCode() { + int result = namespace != null + ? namespace.hashCode() + : 0; + result = 31 * result + (revision != null + ? revision.hashCode() + : 0); + return result; + } +} diff --git a/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/YangTypeLinkIndex.java b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/YangTypeLinkIndex.java new file mode 100644 index 000000000..8220d7f78 --- /dev/null +++ b/vpp-integration/api-docs/core/src/main/java/io/fd/hc2vpp/docs/core/YangTypeLinkIndex.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * + * 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 io.fd.hc2vpp.docs.core; + +import static java.lang.String.format; + +import java.io.IOException; +import java.lang.reflect.Field; +import org.opendaylight.yangtools.yang.common.QName; + +public class YangTypeLinkIndex { + + private final ModelLinkIndex modelLinkIndex; + private final ModelTypeIndex modelTypeIndex; + + public YangTypeLinkIndex(final String projectRoot, final String version) { + modelLinkIndex = new ModelLinkIndex(projectRoot, version); + try { + modelTypeIndex = new ModelTypeIndex(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + public String getLinkForType(final String classname) { + final Class loadedClass; + final QName qname; + try { + loadedClass = this.getClass().getClassLoader().loadClass(classname); + final Field qnameField = loadedClass.getField("QNAME"); + qname = QName.class.cast(qnameField.get(null)); + } catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) { + throw new IllegalStateException(format("Unable to extract QNAME from %s", classname), e); + } + + + final String namespace = qname.getNamespace().toString(); + final String formattedRevision = qname.getFormattedRevision(); + final String model = modelTypeIndex.namespaceToModule(namespace, formattedRevision); + return modelLinkIndex.linkForModel(model, formattedRevision); + } +} -- cgit 1.2.3-korg