Простая реализация 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>