/*
* Copyright 2013 eXo Platform SAS
*
* 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 juzu.impl.plugin.template.metamodel;
import juzu.impl.common.Tools;
import juzu.impl.fs.spi.ReadFileSystem;
import juzu.impl.metamodel.AnnotationChange;
import juzu.impl.plugin.application.metamodel.ApplicationMetaModel;
import juzu.impl.plugin.application.metamodel.ApplicationMetaModelPlugin;
import juzu.impl.plugin.module.metamodel.ModuleMetaModel;
import juzu.impl.metamodel.AnnotationKey;
import juzu.impl.metamodel.AnnotationState;
import juzu.impl.compiler.ProcessingContext;
import juzu.impl.compiler.ElementHandle;
import juzu.impl.metamodel.MetaModelProcessor;
import juzu.impl.template.spi.TemplateProvider;
import juzu.impl.common.JSON;
import juzu.impl.common.Path;
import juzu.template.TagHandler;
import juzu.template.Tags;
import javax.annotation.processing.Completion;
import javax.annotation.processing.Completions;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
/** @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> */
public class TemplateMetaModelPlugin extends ApplicationMetaModelPlugin {
/** . */
private static final Set<Class<? extends Annotation>> PROCESSED = Collections.unmodifiableSet(Tools.<Class<? extends Annotation>>set(juzu.Path.class, Tags.class));
/** . */
public static final Pattern PATH_PATTERN = Pattern.compile("([^/].*/|)([^./]+)\\.([a-zA-Z]+)");
/** The tag loaded from the classpath. */
public final Map<String, TagHandler> tags = new HashMap<String, TagHandler>();
/** . */
Map<String, TemplateProvider> providers;
public TemplateMetaModelPlugin() {
super("template");
}
@Override
public Set<Class<? extends java.lang.annotation.Annotation>> init(ProcessingContext env) {
return PROCESSED;
}
@Override
public void postActivate(ModuleMetaModel applications) {
// Load the tag handlers
for (TagHandler handler : applications.getProcessingContext().loadServices(TagHandler.class)) {
applications.getProcessingContext().info("Loaded tag handler " + handler.getClass().getName() + " as " + handler.getName());
tags.put(handler.getName(), handler);
}
// Load the template providers
Iterable<TemplateProvider> loader = applications.processingContext.loadServices(TemplateProvider.class);
Map<String, TemplateProvider> providers = new HashMap<String, TemplateProvider>();
for (TemplateProvider provider : loader) {
providers.put(provider.getSourceExtension(), provider);
}
//
this.providers = providers;
}
@Override
public void init(ApplicationMetaModel application) {
TemplateContainerMetaModel templates = new TemplateContainerMetaModel();
templates.plugin = this;
application.addChild(TemplateContainerMetaModel.KEY, templates);
TagContainerMetaModel tags = new TagContainerMetaModel();
tags.plugin = this;
application.addChild(TagContainerMetaModel.KEY, tags);
}
@Override
public void processAnnotationChange(ApplicationMetaModel metaModel, AnnotationChange change) {
if (change.getKey().getType().toString().equals(Tags.class.getName())) {
// Read annotation tags
TagContainerMetaModel tagContainer = metaModel.getChild(TagContainerMetaModel.KEY);
HashMap<String, Path.Absolute> tagAnnotations = new HashMap<String, Path.Absolute>();
if (change.getAdded() != null) {
List<AnnotationState> tagsMember = (List<AnnotationState>)change.getAdded().get("value");
if (tagsMember != null) {
for (AnnotationState tag : tagsMember) {
String name = (String)tag.get("name");
Path.Relative relativePath = (Path.Relative)Path.parse((String)tag.get("path"));
Path.Absolute absolutePath = tagContainer.resolvePath(relativePath);
tagAnnotations.put(name, absolutePath);
}
}
}
// Remove annotations that were removed or changed
for (TagMetaModel tag : tagContainer.getChildren(TagMetaModel.class)) {
TemplateMetaModel template = tag.getChild(TemplateMetaModel.KEY);
if (template.getPath().equals(tagAnnotations.get(tag.name))) {
tagAnnotations.remove(tag.name);
} else {
tag.remove();
}
}
// Add missing annotations
for (Map.Entry<String, Path.Absolute> tagAnnotation : tagAnnotations.entrySet()) {
tagContainer.add(tagAnnotation.getKey(), tagAnnotation.getValue());
}
} else {
super.processAnnotationChange(metaModel, change);
}
}
@Override
public void processAnnotationAdded(ApplicationMetaModel application, AnnotationKey key, AnnotationState added) {
if (key.getType().toString().equals(juzu.Path.class.getName())) {
if (key.getElement() instanceof ElementHandle.Field) {
ElementHandle.Field variableElt = (ElementHandle.Field)key.getElement();
TemplateContainerMetaModel templates = application.getChild(TemplateContainerMetaModel.KEY);
Path addedPath = Path.parse((String)added.get("value"));
Path.Absolute absAdded = templates.resolvePath(addedPath);
application.processingContext.info("Adding template ref " + variableElt.getTypeName() + "#" + variableElt.getName() + " " + absAdded);
templates.add(variableElt, absAdded);
}
else {
throw MetaModelProcessor.ANNOTATION_UNSUPPORTED.failure(key);
}
}
}
@Override
public void processAnnotationRemoved(ApplicationMetaModel metaModel, AnnotationKey key, AnnotationState removed) {
if (key.getType().toString().equals(juzu.Path.class.getName())) {
if (key.getElement() instanceof ElementHandle.Field) {
ElementHandle.Field variableElt = (ElementHandle.Field)key.getElement();
TemplateContainerMetaModel templates = metaModel.getChild(TemplateContainerMetaModel.KEY);
Path removedPath = Path.parse((String)removed.get("value"));
Path.Absolute absRemoved = templates.resolvePath(removedPath);
metaModel.processingContext.info("Removing template ref " + variableElt.getTypeName() + "#" + variableElt.getName() + " " + absRemoved);
templates.remove(variableElt);
}
}
}
@Override
public void postActivate(ApplicationMetaModel application) {
application.getChild(TemplateContainerMetaModel.KEY).postActivate(this);
application.getChild(TagContainerMetaModel.KEY).postActivate(this);
}
@Override
public void prePassivate(ApplicationMetaModel application) {
application.processingContext.info("Passivating template resolver for " + application.getHandle());
application.getChild(TemplateContainerMetaModel.KEY).prePassivate();
application.getChild(TagContainerMetaModel.KEY).prePassivate();
}
@Override
public void prePassivate(ModuleMetaModel module) {
module.processingContext.info("Passivating templates");
tags.clear();
this.providers = null;
}
@Override
public void postProcessEvents(ApplicationMetaModel application) {
application.processingContext.info("Processing templates of " + application.getHandle());
application.getChild(TemplateContainerMetaModel.KEY).postProcessEvents();
application.getChild(TagContainerMetaModel.KEY).postProcessEvents();
}
@Override
public Iterable<? extends Completion> getCompletions(
ApplicationMetaModel metaModel,
AnnotationKey annotationKey,
AnnotationState annotationState,
String member, String
userText) {
List<Completion> completions = Collections.emptyList();
ReadFileSystem<File> sourcePath = metaModel.getProcessingContext().getSourcePath(metaModel.getHandle());
if (sourcePath != null) {
try {
final File root = sourcePath.getPath(metaModel.getChild(TemplateContainerMetaModel.KEY).getQN());
if (root.isDirectory()) {
File[] children = root.listFiles();
if (children != null) {
if (userText != null && userText.length() > 0) {
try {
Path path = Path.parse(userText);
if (path.isRelative()) {
File from;
String suffix;
if (path.getExt() == null) {
from = sourcePath.getPath(root, path.getName());
if (from == null) {
from = sourcePath.getPath(root, path.getDirs());
suffix = path.getSimpleName();
} else {
suffix = "";
}
} else {
from = sourcePath.getPath(root, path.getDirs());
suffix = path.getSimpleName();
}
if (from != null) {
completions = list(root, from, suffix);
}
}
}
catch (IllegalArgumentException ignore) {
}
} else {
completions = list(root, root, "");
}
}
}
}
catch (IOException ignore) {
}
}
return completions;
}
private void foo(StringBuilder buffer, File root, File file) {
if (file.equals(root)) {
buffer.setLength(0);
} else {
foo(buffer, root, file.getParentFile());
if (buffer.length() > 0) {
buffer.append('/');
}
buffer.append(file.getName());
}
}
private List<Completion> list(File root, File from, String suffix) {
File[] children = from.listFiles();
StringBuilder path = new StringBuilder();
if (children != null) {
ArrayList<Completion> completions = new ArrayList<Completion>();
for (final File child : children) {
if (child.getName().startsWith(suffix)) {
foo(path, root, child);
if (child.isDirectory()) {
path.append('/');
}
completions.add(Completions.of(path.toString()));
}
}
Collections.sort(completions, Tools.COMPLETION_COMPARATOR);
return completions;
} else {
return Collections.emptyList();
}
}
@Override
public JSON getDescriptor(ApplicationMetaModel application) {
JSON config = new JSON();
AbstractContainerMetaModel metaModel = application.getChild(TemplateContainerMetaModel.KEY);
LinkedHashSet<String> templates = new LinkedHashSet<String>();
for (TemplateRefMetaModel ref : metaModel.getChildren(TemplateRefMetaModel.class)) {
if (ref instanceof ElementMetaModel) {
templates.add(((ElementMetaModel)ref).getPath().getName().toString());
}
}
config.map("templates", templates);
config.set("package", metaModel.getQN().toString());
return config;
}
}