/*
* 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.metamodel;
import juzu.impl.common.Name;
import juzu.impl.compiler.ProcessingContext;
import juzu.impl.compiler.ProcessingException;
import javax.annotation.processing.Completion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
/** @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> */
public final class MetaModelContext<P extends MetaModelPlugin<M, P>, M extends MetaModel<P, M>>
implements Serializable, Iterable<M> {
/** . */
private ProcessingContext processingContext;
/** The meta model. */
private ArrayList<M> metaModels;
/** The plugins. */
private LinkedHashMap<String, P> pluginMap;
/** The supported annotations per plugin. */
private HashMap<P, HashSet<Name>> supportedAnnotationsMap;
/** All supported annotations. */
private Set<Class<? extends java.lang.annotation.Annotation>> supportedAnnotations;
/** . */
private final Class<P> pluginType;
/** All known annotations. */
final LinkedHashMap<AnnotationKey, AnnotationState> knownAnnotations = new LinkedHashMap<AnnotationKey, AnnotationState>();
public MetaModelContext(Class<P> pluginType) {
this.pluginType = pluginType;
this.metaModels = new ArrayList<M>();
}
public void init(ProcessingContext env) throws NullPointerException {
this.processingContext = env;
//
HashMap<P, HashSet<Name>> supportedAnnotationsMap = new HashMap<P, HashSet<Name>>();
LinkedHashMap<String, P> pluginMap = new LinkedHashMap<String, P>();
StringBuilder msg = new StringBuilder("Using plugins:");
for (P plugin : env.loadServices(pluginType)) {
msg.append(" ").append(plugin.getName());
pluginMap.put(plugin.getName(), plugin);
}
env.info(msg);
// Collect processed annotations
HashSet<Class<? extends java.lang.annotation.Annotation>> supportedAnnotations = new HashSet<Class<? extends java.lang.annotation.Annotation>>();
for (P plugin : pluginMap.values()) {
HashSet<Name> pluginSupportedAnnotations = new HashSet<Name>();
for (Class<? extends Annotation> annotationType : plugin.init(env)) {
pluginSupportedAnnotations.add(Name.create(annotationType));
supportedAnnotations.add(annotationType);
}
env.info("Plugin " + plugin.getName() + " supports " + pluginSupportedAnnotations);
supportedAnnotationsMap.put(plugin, pluginSupportedAnnotations);
}
//
this.pluginMap = pluginMap;
this.supportedAnnotationsMap = supportedAnnotationsMap;
this.supportedAnnotations = supportedAnnotations;
}
public Iterator<M> iterator() {
return metaModels.iterator();
}
public Set<Class<? extends java.lang.annotation.Annotation>> getSupportedAnnotations() {
return supportedAnnotations;
}
public Collection<P> getPlugins() {
return pluginMap.values();
}
public void add(M metaModel) {
metaModel.processingContext = processingContext;
metaModel.forward = true;
metaModel.context = this;
metaModel.init(processingContext);
for (P plugin : pluginMap.values()) {
plugin.init(metaModel);
}
metaModels.add(metaModel);
}
public final void postActivate(ProcessingContext processingContext) throws NullPointerException {
this.processingContext = processingContext;
for (M metaModel : metaModels) {
metaModel.processingContext = processingContext;
for (P plugin : pluginMap.values()) {
plugin.postActivate(metaModel);
}
}
}
public void processAnnotationChange(AnnotationChange change) {
// Update state
if (change.getAdded() == null) {
knownAnnotations.remove(change.getKey());
} else {
knownAnnotations.put(change.getKey(), change.getAdded());
}
//
for (M metaModel : metaModels) {
if (metaModel.forward) {
metaModel.forward = false;
for (Map.Entry<AnnotationKey, AnnotationState> annotation : knownAnnotations.entrySet()) {
change = new AnnotationChange(annotation.getKey(), null, annotation.getValue());
for (P plugin : pluginMap.values()) {
HashSet<Name> supportedAnnotations = supportedAnnotationsMap.get(plugin);
if (supportedAnnotations.contains(change.key.type)) {
plugin.processAnnotationChange(metaModel, change);
}
}
}
} else {
for (P plugin : pluginMap.values()) {
HashSet<Name> supportedAnnotations = supportedAnnotationsMap.get(plugin);
if (supportedAnnotations.contains(change.key.type)) {
plugin.processAnnotationChange(metaModel, change);
}
}
}
}
}
public void processAnnotationChanges(Iterable<AnnotationChange> changes) {
for (AnnotationChange change : changes) {
processAnnotationChange(change);
}
}
void processAnnotations(Iterable<Map.Entry<AnnotationKey, AnnotationState>> annotations) {
ArrayList<AnnotationChange> delta = new ArrayList<AnnotationChange>();
for (Map.Entry<AnnotationKey, AnnotationState> entry : knownAnnotations.entrySet()) {
AnnotationKey key = entry.getKey();
Element element = processingContext.get(key.element);
if (element == null) {
delta.add(new AnnotationChange(key, entry.getValue(), null));
processingContext.log(Level.FINER, "Annotation removed " + key);
} else {
AnnotationMirror found = null;
for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
Name f = Name.parse(((TypeElement)mirror.getAnnotationType().asElement()).getQualifiedName().toString());
if (key.getType().equals(f)) {
found = mirror;
break;
}
}
if (found == null) {
delta.add(new AnnotationChange(key, entry.getValue(), null));
processingContext.log(Level.FINER, "Annotation removed " + key);
}
}
}
for (Map.Entry<AnnotationKey, AnnotationState> annotation : annotations) {
AnnotationState knownAnnotation = knownAnnotations.get(annotation.getKey());
if (knownAnnotation != null) {
processingContext.log(Level.FINER, "Annotation updated " + annotation.getKey());
} else {
processingContext.log(Level.FINER, "Annotation added " + annotation.getKey());
}
delta.add(new AnnotationChange(annotation.getKey(), knownAnnotation, annotation.getValue()));
}
processAnnotationChanges(delta);
}
public Iterable<? extends Completion> getCompletions(
AnnotationKey annotationKey,
AnnotationState annotationState,
String member,
String userText) {
Iterable<? extends Completion> completions = null;
for (M metaModel : metaModels) {
for (P plugin : pluginMap.values()) {
HashSet<Name> supportedAnnotations = supportedAnnotationsMap.get(plugin);
if (supportedAnnotations.contains(annotationKey.type)) {
completions = plugin.getCompletions(metaModel, annotationKey, annotationState, member, userText);
if (completions != null) {
break;
}
}
}
}
return completions;
}
public void postProcessAnnotations() throws ProcessingException {
for (M metaModel : metaModels) {
for (P plugin : pluginMap.values()) {
plugin.postProcessAnnotations(metaModel);
}
}
}
public void processEvents() {
for (M metaModel : metaModels) {
for (P plugin : pluginMap.values()) {
plugin.processEvents(metaModel, new EventQueue(metaModel.dispatch));
}
metaModel.dispatch.clear();
}
}
public void postProcessEvents() {
for (M metaModel : metaModels) {
for (P plugin : pluginMap.values()) {
plugin.postProcessEvents(metaModel);
}
}
}
public void prePassivate() {
for (M metaModel : metaModels) {
for (P plugin : pluginMap.values()) {
plugin.prePassivate(metaModel);
}
metaModel.processingContext = null;
}
this.processingContext = null;
}
public void remove(M metaModel) {
try {
metaModel.processingContext = processingContext;
//
metaModels.remove(metaModel);
// Initialize plugins
for (P plugin : pluginMap.values()) {
plugin.destroy(metaModel);
}
}
finally {
metaModel.processingContext = null;
}
}
}