/*
* Copyright (c) 2009-2015
* IT-Consulting Stephan Schloepke (http://www.schloepke.de/)
* klemm software consulting Mirko Klemm (http://www.klemm-scs.com/)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jbasics.parser;
import org.jbasics.parser.annotations.*;
import org.jbasics.parser.invoker.AttributeInvoker;
import org.jbasics.parser.invoker.ContentInvoker;
import org.jbasics.parser.invoker.ElementInvoker;
import org.jbasics.parser.invoker.Invoker;
import org.jbasics.parser.invoker.QualifiedNameInvoker;
import org.jbasics.pattern.builder.Builder;
import org.jbasics.pattern.builder.ReflectionBuilderFactory;
import org.jbasics.pattern.factory.Factory;
import org.jbasics.types.factories.ReflectionFactory;
import org.jbasics.types.tuples.Pair;
import javax.xml.namespace.QName;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
@SuppressWarnings("unchecked")
public class AnnotationScanner {
// private final Map<Class<? extends Builder>, ParsingInfo> parsedBuilders = new HashMap<Class<? extends Builder>, ParsingInfo>();
public Map<QName, ParsingInfo> scan(Class<?>... root) {
if (root == null || root.length == 0) {
throw new IllegalArgumentException("Null or empty parameter: builderTypes[]");
}
Map<QName, ParsingInfo> result = new LinkedHashMap<QName, ParsingInfo>();
ParsingInfoBuilder builder = new ParsingInfoBuilder();
for (Class<?> impl : root) {
ElementImplementor elementImplementor = impl.getAnnotation(ElementImplementor.class);
ElementImplementors elementImplementos = impl.getAnnotation(ElementImplementors.class);
// Ok now we do have all the possible implementors Scan them
if (elementImplementos != null) {
for (ElementImplementor implementor : elementImplementos.value()) {
builder.reset();
Pair<QName, ParsingInfoBuilder> temp = scanElementImplementor(builder, implementor);
result.put(temp.first(), temp.second().build());
}
}
if (elementImplementor != null) {
builder.reset();
Pair<QName, ParsingInfoBuilder> temp = scanElementImplementor(builder, elementImplementor);
result.put(temp.first(), temp.second().build());
}
}
return result;
}
private Pair<QName, ParsingInfoBuilder> scanElementImplementor(ParsingInfoBuilder builder,
ElementImplementor implementor) {
QName name = new QName(implementor.namespace(), implementor.localName());
Class<?> clazz = implementor.builderClass();
return new Pair<QName, ParsingInfoBuilder>(name, scan(builder, clazz));
}
private <T> ParsingInfoBuilder scan(ParsingInfoBuilder builder, Class<T> implClass) {
ElementBuilder temp = implClass.getAnnotation(ElementBuilder.class);
Class<? extends Builder> builderType;
Factory<? extends Builder> factory;
if (temp != null) {
builderType = temp.value();
factory = ReflectionFactory.create(builderType);
} else {
ReflectionBuilderFactory<T> tempFactory = ReflectionBuilderFactory.createFactory(implClass);
builderType = tempFactory.getBuilderClass();
factory = tempFactory;
}
return scanType(builder, builderType, factory);
}
private ParsingInfoBuilder scanType(ParsingInfoBuilder builder, Class<? extends Builder> builderType,
Factory<? extends Builder> builderFactory) {
assert builderType != null;
builder.setBuilderType(builderType);
if (builderFactory != null) {
builder.setBuilderFactory(builderFactory);
} else {
builder.setBuilderFactory(ReflectionFactory.create(builderType));
}
for (Method m : builderType.getMethods()) {
QualifiedName qualifiedName = m.getAnnotation(QualifiedName.class);
Attribute directAttribute = m.getAnnotation(Attribute.class);
AnyAttribute anyAttribute = m.getAnnotation(AnyAttribute.class);
Element directElement = m.getAnnotation(Element.class);
AnyElement anyElement = m.getAnnotation(AnyElement.class);
Content contentElement = m.getAnnotation(Content.class);
Comment commentElement = m.getAnnotation(Comment.class);
if (isMoreThanNotNull(qualifiedName, directAttribute, anyAttribute, directElement, anyElement,
contentElement, commentElement)) {
throw new IllegalArgumentException("Cannot have more than one type of annotaion on method "
+ m.getName());
}
if (qualifiedName != null) {
builder.setQualifiedName(QualifiedNameInvoker.createInvoker(builderType, m));
} else if (contentElement != null) {
processContent(builder, builderType, m, contentElement);
} else if (commentElement != null) {
processComment(builder, builderType, m, commentElement);
} else if (directAttribute != null || anyAttribute != null) {
processAttribute(builder, builderType, m, directAttribute, anyAttribute);
} else if (directElement != null || anyElement != null) {
processElement(builder, m, directElement, anyElement);
}
}
return builder;
}
private void processContent(ParsingInfoBuilder builder, Class<? extends Builder> builderType, Method m,
Content contentElement) {
if (contentElement.mixed()) {
throw new UnsupportedOperationException("Mixed content is not yet implemented");
} else {
builder.setContentInvoker(ContentInvoker.createInvoker(builderType, m));
}
}
private void processComment(ParsingInfoBuilder builder, Class<? extends Builder> builderType, Method m, Comment commentElement) {
builder.setCommentInvoker(ContentInvoker.createInvoker(builderType, m));
}
private ParsingInfoBuilder processAttribute(ParsingInfoBuilder builder, Class<? extends Builder> builderType,
Method m, Attribute directAttribute, AnyAttribute anyAttribute) {
assert m != null && (directAttribute != null || anyAttribute != null);
if (directAttribute != null) {
QName qualifiedName = new QName(directAttribute.namespace(), directAttribute.name());
builder.addAttribute(qualifiedName, AttributeInvoker.createInvoker(builderType, m));
} else if (anyAttribute != null) {
builder.setAnyAttribute(AttributeInvoker.createInvoker(builderType, m));
}
return builder;
}
private ParsingInfoBuilder processElement(ParsingInfoBuilder builder, Method m, Element directElement,
AnyElement anyElement) {
assert m != null && (directElement != null || anyElement != null);
Class<?>[] params = m.getParameterTypes();
Class<?> type = null;
if (params.length == 1) {
type = params[0];
} else {
throw new IllegalArgumentException("Wrong signature for element method");
}
// Now we have the type. We need to figure out the builder of this type. There are multiple
// ways to solve this.
// 1. The static newBuilder() methods should return an instance of the builder therefor the
// return type would be the builder class
// 2. Annotate the builder class at the instance type with a BuilderAnnotation
// 3. Annotate the builder in the element description
Class<? extends Builder> subBuilderType = null;
Factory<? extends Builder> subBuilderFactory = null;
try {
ElementBuilder temp = m.getAnnotation(ElementBuilder.class);
if (temp == null) {
temp = type.getAnnotation(ElementBuilder.class);
}
if (temp != null) {
subBuilderType = temp.value();
subBuilderFactory = ReflectionFactory.create(subBuilderType);
} else {
ReflectionBuilderFactory<?> tempFactory = ReflectionBuilderFactory.createFactory(type);
subBuilderType = tempFactory.getBuilderClass();
subBuilderFactory = tempFactory;
}
} catch (RuntimeException e) {
subBuilderType = SimpleTypeBuilder.class;
subBuilderFactory = SimpleTypeBuilder.BuilderFactory.createFactory(type);
}
ParsingInfo subInfo = null;
if (subBuilderType == builder.getBuilderType()) {
subInfo = ParsingInfo.SELF;
} else {
subInfo = scanType(new ParsingInfoBuilder(), subBuilderType, subBuilderFactory).build();
}
// TODO: The builderType cannot be right here can it? It should be the builder on which the
// element to add right? That however is another
// one than the builder of the element we now want to add! So the parsing info could
// possible have the right builder set
Pair<ParsingInfo, Invoker<?, ?>> x = new Pair<ParsingInfo, Invoker<?, ?>>(subInfo, ElementInvoker
.createInvoker(builder.getBuilderType(), type, m));
if (directElement != null) {
QName qualifiedName = new QName(directElement.namespace(), directElement.name());
builder.addElement(qualifiedName, x);
} else if (anyElement != null) {
builder.setAnyElement(x);
}
return builder;
}
private boolean isMoreThanNotNull(Object... elements) {
boolean foundOne = false;
for (Object temp : elements) {
if (temp != null) {
if (foundOne) {
return true;
} else {
foundOne = true;
}
}
}
return false;
}
}