Простая реализация SourceCodeViewer будет иметь сервис для добавления и перечисления исходного кода и компонент для его отображения.
public interface SourceCodeService { /** * This method contributes a source code resource. * * @param sourceCodeContribution */ void add(SourceCodeResource sourceCodeContribution); /** * This method contributes a source code resource. * * @param type * @param resource */ void add(String type, Resource resource); /** * This method contributes an optional source code resource. * * @param type * @param resource */ void addOptional(String type, Resource resource); /** * This method adds a java source code for a given class. * * @param clazz */ void add(Class<?> clazz); /** * This method contributes a java and template file * * @param clazz */ void addComponent(Class<?> clazz); /** * This method adds a java and template file for a * component(page/component/mixin) with the given name. * * @param componentName */ void addComponent(String componentName); /** * @return all source code resources. */ Set<SourceCodeResource> getSourceCodeResources(); } //Implementation public class SourceCodeServiceImpl implements SourceCodeService { private final Set<SourceCodeResource> resources = new HashSet<SourceCodeResource>(); private final AssetSource assetSource; public SourceCodeServiceImpl(final AssetSource assetSource) { this.assetSource = assetSource; } @Override public void add(final SourceCodeResource sourceCodeContribution) { this.resources.add(sourceCodeContribution); } @Override public void add(final String type, final Resource resource) { add(new SourceCodeResource(type, resource)); } @Override public Set<SourceCodeResource> getSourceCodeResources() { return this.resources; } @Override public void add(final Class<?> clazz) { add(SourceCodeResource.JAVA, getResourcePathFromClass(clazz, "java")); } @Override public void addComponent(final Class<?> clazz) { add(clazz); add(SourceCodeResource.TML, getResourcePathFromClass(clazz, "tml")); addOptional(SourceCodeResource.PROPERTIES, getResourcePathFromClass(clazz, "properties")); } @Override public void addComponent(final String componentClass) { add(SourceCodeResource.JAVA, getResourcePathFromClassName(componentClass, "java")); addOptional(SourceCodeResource.TML, getResourcePathFromClassName(componentClass, "tml")); addOptional(SourceCodeResource.PROPERTIES, getResourcePathFromClassName(componentClass, "properties")); } private Resource getResourcePathFromClassName(final String componentClass, final String extension) { return this.assetSource.resourceForPath("classpath:/" + componentClass.replace('.', '/') + "." + extension); } private Resource getResourcePathFromClass(final Class<?> clazz, final String extension) { return this.assetSource.resourceForPath("classpath:/" + clazz.getCanonicalName().replace('.', '/') + "." + extension); } @Override public void addOptional(final String type, final Resource resource) { add(new SourceCodeResource(type, resource, true)); } } public class SourceCodeResource { /** CSS resource type */ public static final String CSS = "css"; /** JavaScript resource type */ public static final String JS = "js"; /** Java resource type */ public static final String JAVA = "java"; /** Tapestry Template resource type */ public static final String TML = "xml"; /** Properties file resource type */ public static final String PROPERTIES = "properties"; private final String type; private final Resource resource; private final boolean optional; public SourceCodeResource(final String type, final Resource resource, final boolean optional) { this.type = type; this.resource = resource; this.optional = optional; } public SourceCodeResource(final String type, final Resource resource) { this(type, resource, false); } public String getType() { return this.type; } public Resource getResource() { return this.resource; } public boolean getOptional() { return this.optional; } @Override public int hashCode() { return this.resource.hashCode(); } @Override public boolean equals(final Object other) { if (other == this) { return true; } if (other == null || !(other instanceof SourceCodeResource)) { return false; } final SourceCodeResource otherResource = (SourceCodeResource) other; return this.resource.equals(otherResource.getResource()); } }
SourceCodeViewer предполагает, что исходный код был скопирован в каталог build / target.
public class SourceCodeViewer { /** Class name. */ private static final String CLASS_NAME = SourceCodeViewer.class .getSimpleName(); @Inject private SourceCodeService sourceCodeService; @Property private SourceCodeResource sourceCodeResource; @Property private List<SourceCodeResource> sourceCodeResources; @SuppressWarnings("unused") @Property private int index; @SetupRender void setupSourceCodeResources() { this.sourceCodeResources = new ArrayList<SourceCodeResource>( this.sourceCodeService.getSourceCodeResources()); Collections.sort(this.sourceCodeResources, new Comparator<SourceCodeResource>() { @Override public int compare(final SourceCodeResource o1, final SourceCodeResource o2) { final int result = o1.getType().toLowerCase() .compareTo(o2.getType().toLowerCase()); if (result != 0) { return result; } return o1 .getResource() .getFile() .toLowerCase() .compareTo( o2.getResource().getFile() .toLowerCase()); } }); } /** * @return resource exists */ public boolean getResourceExists(){ return !this.sourceCodeResource.getOptional() || this.sourceCodeResource.getResource().exists(); } /** * @return String */ public String getSource() { // Declare/Initialize final ByteArrayOutputStream outputStream; try { final Resource resource = this.sourceCodeResource.getResource(); outputStream = new ByteArrayOutputStream(); TapestryInternalUtils.copy(resource.openStream(), outputStream); } catch (final Exception e) { throw new RuntimeException( "The source code [" + this.sourceCodeResource.getResource() + "] is not available.", e); } return new String(outputStream.toByteArray()); } }
<t:container xmlns:t='http://tapestry.apache.org/schema/tapestry_5_3.xsd'> <div class='accordion' id='sourceAccordion'> <t:loop t:source='sourceCodeResources' t:value='sourceCodeResource' t:index='index'> <t:if test='resourceExists'> <div class='accordion-group'> <div class='accordion-heading'> <a href='#Resource_${index}' class='accordion-toggle' data-toggle='collapse' data-parent='#sourceAccordion'> ${sourceCodeResource.resource.path} </a> </div> <div id='Resource_${index}' class='accordion-body collapse in'> <pre class='${sourceCodeResource.type}' name='Resource_${index}'>${source}</pre> </div> </div> </t:if> </t:loop> </div> </t:container>
Теперь давайте применим к нему магию гобелена. Мы можем использовать преобразования классов для автоматического добавления исходного кода для компонентов / страниц / смесей.
@Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface ShowSourceCode { /** * This method adds additional classes to be contributed to * {@link SourceCodeService} * * @return additional classes to be contributed */ Class<?>[] additionalClasses() default {}; /** * Resources for which source code to be attached. * * @return resources */ SourceCode[] resources() default {}; /** * Whether to use resources imported via {@link Import} * @return use import. */ boolean useImport() default true; } @Documented @Retention(RetentionPolicy.RUNTIME) public @interface SourceCode { /** * The resource path which uses the same path pattern as tapestry. * * @return resource path. */ String value(); /** * Resource type. * * @return resource type */ String type() default "xml"; /** * Is the source-code optional. If set to false and the resource is not * found an exception is thrown. * * @return optional */ boolean optional() default false; }
Таким образом, эта аннотация позволяет нам добавлять исходный код четырьмя различными способами.
- Просто аннотируйте класс component / page / mixins с помощью @ShowSourceCode, чтобы добавить их исходный код.
- Использование @ ShowSourceCode # useImport () для включения исходного кода из аннотации @Import
- Используйте @ ShowSourceCode # AdditionalClasses для включения дополнительных (некомпонентных) классов
- Используйте ресурсы @ ShowSourceCode # для добавления дополнительных ресурсов.
Все это делается реализацией ComponentClassTransformWorker2
public class ShowSourceCodeWorker implements ComponentClassTransformWorker2 { private final SourceCodeService sourceCodeService; private final AssetSource assetSource; public ShowSourceCodeWorker(final SourceCodeService sourceCodeService, final AssetSource assetSource) { this.sourceCodeService = sourceCodeService; this.assetSource = assetSource; } @Override public void transform(final PlasticClass plasticClass, @SuppressWarnings("unused") final TransformationSupport support, final MutableComponentModel model) { if (plasticClass.hasAnnotation(ShowSourceCode.class)) { final PlasticMethod setupRender = plasticClass .introduceMethod(TransformConstants.SETUP_RENDER_DESCRIPTION); final ShowSourceCode showSourceCode = plasticClass .getAnnotation(ShowSourceCode.class); addAdvice(setupRender, model.getBaseResource(), showSourceCode, plasticClass.getAnnotation(Import.class)); model.addRenderPhase(SetupRender.class); } } private void addAdvice(final PlasticMethod method, final Resource baseResource, final ShowSourceCode showSourceCodeAnnotation, final Import importAnnotation) { method.addAdvice(new MethodAdvice() { @Override public void advise(final MethodInvocation invocation) { final ComponentResources resources = invocation .getInstanceContext().get(ComponentResources.class); final Class<?> pageClass = invocation.getInstanceContext() .getInstanceType(); addComponent(pageClass); addAdditionalClasses(showSourceCodeAnnotation.additionalClasses()); addResources(showSourceCodeAnnotation.resources(), resources.getLocale(), baseResource); if (showSourceCodeAnnotation.useImport() && importAnnotation != null) { addAssets(SourceCodeResource.JS, importAnnotation.library(), baseResource, resources.getLocale()); addAssets(SourceCodeResource.CSS, importAnnotation.stylesheet(), baseResource, resources.getLocale()); } invocation.proceed(); } }); } private void addResources(final SourceCode[] sourceCodes, final Locale locale, final Resource baseResource) { for (final SourceCode sourceCode : sourceCodes) { this.sourceCodeService.add(new SourceCodeResource( sourceCode.type(), this.assetSource.getAsset(baseResource, sourceCode.value(), locale).getResource(), sourceCode.optional())); } } private void addComponent(final Class<?> pageClass) { this.sourceCodeService.addComponent(pageClass); } private void addAdditionalClasses(final Class<?>[] additionalClasses) { for (final Class<?> clazz : additionalClasses) { this.sourceCodeService.add(clazz); } } private void addAssets(final String type, final String[] libraries, final Resource baseResource, final Locale locale) { for (final String library : libraries) { this.sourceCodeService.add(type, this.assetSource.getAsset( baseResource, library, locale).getResource()); } } }
Работник использует SourceCodeService для добавления различных ресурсов. Для компонентов мы используем AssetSource для получения ресурсов относительно компонентов.
Наконец, мы должны добавить ComponentClassTransformWorker2 в AppModule
public static void bind(final ServiceBinder binder) { binder.bind(SourceCodeService.class, SourceCodeServiceImpl.class) .scope(ScopeConstants.PERTHREAD); } @Contribute(ComponentClassTransformWorker2.class) public static void contributeWorkers( final OrderedConfiguration<ComponentClassTransformWorker2> workers) { workers.addInstance("ShowSourceCode", ShowSourceCodeWorker.class, "after:RenderPhase,before:Import"); } }
Не забудьте скопировать исходный код Java. Для maven2 вы можете сделать это, добавив это в pom.xml
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <version>2.1.2</version> <executions> <execution> <id>attach-sources</id> <phase>verify</phase> <goals> <goal>jar-no-fork</goal> </goals> </execution> </executions> </plugin>