Java tutorial
/* * Copyright 2015 Alexey Andreev. * * 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 org.teavm.flavour.templates.parsing; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; import net.htmlparser.jericho.Attribute; import net.htmlparser.jericho.CharacterReference; import net.htmlparser.jericho.Element; import net.htmlparser.jericho.Segment; import net.htmlparser.jericho.Source; import net.htmlparser.jericho.StartTag; import net.htmlparser.jericho.StartTagType; import net.htmlparser.jericho.Tag; import org.apache.commons.lang3.StringUtils; import org.teavm.flavour.expr.ClassResolver; import org.teavm.flavour.expr.Compiler; import org.teavm.flavour.expr.Diagnostic; import org.teavm.flavour.expr.ImportingClassResolver; import org.teavm.flavour.expr.Location; import org.teavm.flavour.expr.Scope; import org.teavm.flavour.expr.TypeEstimator; import org.teavm.flavour.expr.TypeUtil; import org.teavm.flavour.expr.TypedPlan; import org.teavm.flavour.expr.ast.Expr; import org.teavm.flavour.expr.ast.LambdaExpr; import org.teavm.flavour.expr.plan.LambdaPlan; import org.teavm.flavour.expr.type.GenericArray; import org.teavm.flavour.expr.type.GenericClass; import org.teavm.flavour.expr.type.GenericMethod; import org.teavm.flavour.expr.type.GenericReference; import org.teavm.flavour.expr.type.GenericType; import org.teavm.flavour.expr.type.GenericTypeNavigator; import org.teavm.flavour.expr.type.MapSubstitutions; import org.teavm.flavour.expr.type.TypeInference; import org.teavm.flavour.expr.type.TypeVar; import org.teavm.flavour.expr.type.ValueType; import org.teavm.flavour.expr.type.meta.ClassDescriber; import org.teavm.flavour.expr.type.meta.ClassDescriberRepository; import org.teavm.flavour.expr.type.meta.MethodDescriber; import org.teavm.flavour.templates.tree.AttributeDirectiveBinding; import org.teavm.flavour.templates.tree.DOMElement; import org.teavm.flavour.templates.tree.DOMText; import org.teavm.flavour.templates.tree.DirectiveBinding; import org.teavm.flavour.templates.tree.DirectiveFunctionBinding; import org.teavm.flavour.templates.tree.DirectiveVariableBinding; import org.teavm.flavour.templates.tree.NestedDirectiveBinding; import org.teavm.flavour.templates.tree.TemplateNode; /** * * @author Alexey Andreev */ public class Parser { private ClassDescriberRepository classRepository; private ImportingClassResolver classResolver; private ResourceProvider resourceProvider; private GenericTypeNavigator typeNavigator; private Map<String, List<DirectiveMetadata>> avaliableDirectives = new HashMap<>(); private Map<String, List<AttributeDirectiveMetadata>> avaliableAttrDirectives = new HashMap<>(); private Map<String, DirectiveMetadata> directives = new HashMap<>(); private Map<String, AttributeDirectiveMetadata> attrDirectives = new HashMap<>(); private List<Diagnostic> diagnostics = new ArrayList<>(); private Map<String, Deque<ValueType>> variables = new HashMap<>(); private Source source; private int position; public Parser(ClassDescriberRepository classRepository, ClassResolver classResolver, ResourceProvider resourceProvider) { this.classRepository = classRepository; this.classResolver = new ImportingClassResolver(classResolver); this.resourceProvider = resourceProvider; this.classResolver.importPackage("java.lang"); this.typeNavigator = new GenericTypeNavigator(classRepository); } public List<Diagnostic> getDiagnostics() { return diagnostics; } public boolean wasSuccessful() { return diagnostics.isEmpty(); } public List<TemplateNode> parse(Reader reader, String className) throws IOException { source = new Source(reader); use(source, "std", "org.teavm.flavour.directives.standard"); use(source, "event", "org.teavm.flavour.directives.events"); use(source, "attr", "org.teavm.flavour.directives.attributes"); use(source, "html", "org.teavm.flavour.directives.html"); pushVar("this", new GenericClass(className)); position = source.getBegin(); List<TemplateNode> nodes = new ArrayList<>(); parseSegment(source.getEnd(), nodes, elem -> true); popVar("this"); source = null; return nodes; } private void parseSegment(int limit, List<TemplateNode> result, Predicate<Element> filter) { while (position < limit) { Tag tag = source.getNextTag(position); if (tag == null || tag.getBegin() > limit) { if (position < limit) { DOMText text = new DOMText(parseText(position, limit)); text.setLocation(new Location(position, limit)); result.add(text); } position = limit; break; } if (position < tag.getBegin()) { DOMText text = new DOMText(parseText(position, tag.getBegin())); text.setLocation(new Location(position, tag.getBegin())); result.add(text); } position = tag.getEnd(); parseTag(tag, result, filter); } } private String parseText(int start, int end) { StringBuilder sb = new StringBuilder(); while (start < end) { CharacterReference ref = source.getNextCharacterReference(start); if (ref == null || ref.getBegin() >= end) { break; } sb.append(source.subSequence(start, ref.getBegin())); sb.append(ref.getChar()); start = ref.getEnd(); } sb.append(source.subSequence(start, end)); return sb.toString(); } private void parseTag(Tag tag, List<TemplateNode> result, Predicate<Element> filter) { if (tag instanceof StartTag) { StartTag startTag = (StartTag) tag; if (startTag.getStartTagType() == StartTagType.XML_PROCESSING_INSTRUCTION) { parseProcessingInstruction(startTag); } else if (startTag.getStartTagType() == StartTagType.NORMAL) { if (filter.test(tag.getElement())) { TemplateNode node = parseElement(tag.getElement()); if (node != null) { result.add(node); } } else { position = tag.getElement().getEnd(); } } } } private TemplateNode parseElement(Element elem) { if (elem.getName().indexOf(':') > 0) { return parseDirective(elem); } else { return parseDomElement(elem); } } private TemplateNode parseDomElement(Element elem) { DOMElement templateElem = new DOMElement(elem.getName()); templateElem.setLocation(new Location(elem.getBegin(), elem.getEnd())); for (int i = 0; i < elem.getAttributes().size(); ++i) { Attribute attr = elem.getAttributes().get(i); if (attr.getName().indexOf(':') > 0) { AttributeDirectiveBinding directive = parseAttributeDirective(attr); if (directive != null) { templateElem.getAttributeDirectives().add(directive); } } else { templateElem.setAttribute(attr.getName(), attr.getValue(), new Location(attr.getBegin(), attr.getEnd())); } } Set<String> vars = new HashSet<>(); for (AttributeDirectiveBinding attrDirective : templateElem.getAttributeDirectives()) { for (DirectiveVariableBinding var : attrDirective.getVariables()) { vars.add(var.getName()); pushVar(var.getName(), var.getValueType()); } } parseSegment(elem.getEnd(), templateElem.getChildNodes(), child -> true); for (String var : vars) { popVar(var); } return templateElem; } private TemplateNode parseDirective(Element elem) { int prefixLength = elem.getName().indexOf(':'); String prefix = elem.getName().substring(0, prefixLength); String name = elem.getName().substring(prefixLength + 1); String fullName = prefix + ":" + name; DirectiveMetadata directiveMeta = resolveDirective(prefix, name); if (directiveMeta == null) { error(elem.getStartTag().getNameSegment(), "Undefined directive " + fullName); return null; } List<PostponedDirectiveParse> postponedList = new ArrayList<>(); TemplateNode node = parseDirective(directiveMeta, prefix, name, elem, postponedList, new MapSubstitutions(new HashMap<>())); completeDirectiveParsing(postponedList); position = elem.getEnd(); return node; } private DirectiveBinding parseDirective(DirectiveMetadata directiveMeta, String prefix, String name, Element elem, List<PostponedDirectiveParse> postponed, MapSubstitutions typeVars) { DirectiveBinding directive = new DirectiveBinding(directiveMeta.cls.getName(), name); directive.setLocation(new Location(elem.getBegin(), elem.getEnd())); if (directiveMeta.nameSetter != null) { directive.setDirectiveNameMethodName(directiveMeta.nameSetter.getName()); } List<PostponedAttributeParse> attributesParse = new ArrayList<>(); for (DirectiveAttributeMetadata attrMeta : directiveMeta.attributes.values()) { Attribute attr = elem.getAttributes().get(attrMeta.name); if (attr == null) { if (attrMeta.required) { error(elem.getStartTag(), "Missing required attribute: " + attrMeta.name); } continue; } if (attrMeta.type == null || attrMeta.valueType == null) { continue; } PostponedAttributeParse attrParse = new PostponedAttributeParse(); attrParse.meta = attrMeta; attrParse.node = attr; if (attrMeta.type == DirectiveAttributeType.FUNCTION || attrMeta.type == DirectiveAttributeType.BIDIRECTIONAL) { attrParse.expr = parseExpr(attr.getValueSegment()); } if (attrParse.meta.valueType != null) { attrParse.type = attrParse.meta.valueType.substitute(typeVars); } if (attrParse.meta.sam != null) { attrParse.sam = attrParse.meta.sam.substitute(typeVars); } if (attrParse.meta.altSam != null) { attrParse.altSam = attrParse.meta.altSam.substitute(typeVars); } attributesParse.add(attrParse); } for (Attribute attr : elem.getAttributes()) { if (!directiveMeta.attributes.containsKey(attr.getName())) { error(attr, "Unknown attribute " + attr.getName() + " for directive " + prefix + ":" + name); } } PostponedDirectiveParse directiveParse = new PostponedDirectiveParse(position, directiveMeta, directive, elem); directiveParse.attributes.addAll(attributesParse); postponed.add(directiveParse); Map<NestedDirective, Set<TypeVar>> nestedNewVars = new HashMap<>(); for (NestedDirective nestedDirective : directiveMeta.nestedDirectives) { Set<TypeVar> newVars = new HashSet<>(); newVariables(typeVars.getMap().keySet(), newVars, nestedDirective.setter.getActualArgumentTypes()[0]); nestedNewVars.put(nestedDirective, newVars); } parseSegment(elem.getEnd(), new ArrayList<>(), child -> { int nestedPrefixLength = child.getName().indexOf(':'); if (nestedPrefixLength > 0) { String nestedPrefix = child.getName().substring(0, nestedPrefixLength); String nestedName = child.getName().substring(nestedPrefixLength + 1); if (nestedPrefix.equals(prefix)) { NestedDirective nested = resolveNestedDirective(directiveMeta, nestedName); if (nested != null) { Set<TypeVar> newVars = nestedNewVars.get(nested); for (TypeVar var : newVars) { typeVars.getMap().put(var, new GenericReference(new TypeVar())); } DirectiveBinding nestedNode = parseDirective(nested.metadata, prefix, nestedName, child, postponed, typeVars); typeVars.getMap().keySet().removeAll(newVars); NestedDirectiveBinding binding = getNestedDirectiveBinding(directive, nested); binding.getDirectives().add(nestedNode); } } } return false; }); validateNestedDirectives(directive, directiveMeta, elem, prefix); return directive; } private void newVariables(Set<TypeVar> fixedVars, Set<TypeVar> result, ValueType type) { if (type instanceof GenericClass) { for (GenericType arg : ((GenericClass) type).getArguments()) { newVariables(fixedVars, result, arg); } } else if (type instanceof GenericArray) { newVariables(fixedVars, result, ((GenericArray) type).getElementType()); } else if (type instanceof GenericReference) { TypeVar var = ((GenericReference) type).getVar(); if (!fixedVars.contains(var)) { result.add(var); } } } private void completeDirectiveParsing(List<PostponedDirectiveParse> postponed) { // Attempting to infer values for directive's type parameters TypeEstimator estimator = new TypeEstimator(classResolver, typeNavigator, new TemplateScope()); TypeInference inference = new TypeInference(typeNavigator); boolean inferenceFailed = false; for (PostponedDirectiveParse parse : postponed) { for (PostponedAttributeParse attrParse : parse.attributes) { if (attrParse.expr != null && attrParse.meta.type == DirectiveAttributeType.FUNCTION) { if (attrParse.expr instanceof LambdaExpr) { attrParse.typeEstimate = estimator.estimateLambda((LambdaExpr<Void>) attrParse.expr, attrParse.sam); } else { attrParse.typeEstimate = estimator.estimate(attrParse.expr); } if (attrParse.typeEstimate != null && !inferenceFailed) { inferenceFailed |= !TypeUtil.subtype(attrParse.typeEstimate, attrParse.sam.getActualReturnType(), inference); } } } } if (inferenceFailed) { inference = new TypeInference(typeNavigator); } // Process functions TypeInference varInference = new TypeInference(typeNavigator); for (PostponedDirectiveParse parse : postponed) { for (PostponedAttributeParse attrParse : parse.attributes) { if (attrParse.expr == null) { continue; } MethodDescriber setter = attrParse.meta.setter; GenericType type = attrParse.sam.getActualOwner().substitute(inference.getSubstitutions()); TypedPlan plan = compileExpr(attrParse.node.getValueSegment(), attrParse.expr, (GenericClass) type); if (plan == null) { continue; } DirectiveFunctionBinding computationBinding = new DirectiveFunctionBinding( setter.getOwner().getName(), setter.getName(), (LambdaPlan) plan.getPlan(), attrParse.sam.getActualOwner().getName()); parse.directive.getComputations().add(computationBinding); varInference.equalConstraint((GenericType) plan.getType(), attrParse.sam.getActualOwner()); if (attrParse.meta.type == DirectiveAttributeType.BIDIRECTIONAL) { setter = attrParse.meta.altSetter; type = attrParse.altSam.getActualOwner().substitute(inference.getSubstitutions()); plan = compileExpr(attrParse.node.getValueSegment(), attrParse.expr, (GenericClass) type); computationBinding = new DirectiveFunctionBinding(setter.getOwner().getName(), setter.getName(), (LambdaPlan) plan.getPlan(), attrParse.altSam.getActualOwner().getName()); parse.directive.getComputations().add(computationBinding); } } } // Process variables Map<String, ValueType> declaredVars = new HashMap<>(); for (PostponedDirectiveParse parse : postponed) { for (PostponedAttributeParse attrParse : parse.attributes) { if (attrParse.meta.type != DirectiveAttributeType.VARIABLE) { continue; } MethodDescriber getter = attrParse.meta.getter; String varName = attrParse.node.getValue(); ValueType type = attrParse.type; if (type instanceof GenericType) { type = ((GenericType) type).substitute(varInference.getSubstitutions()); } if (declaredVars.containsKey(varName)) { error(attrParse.node.getValueSegment(), "Variable " + varName + " is already used by " + "the same directive"); } else { declaredVars.put(varName, type); pushVar(varName, type); } DirectiveVariableBinding varBinding = new DirectiveVariableBinding(getter.getOwner().getName(), getter.getName(), varName, getter.getRawReturnType(), type); parse.directive.getVariables().add(varBinding); } } // Process bodies Set<Element> elementsToSkip = postponed.stream().map(parse -> parse.elem).collect(Collectors.toSet()); for (PostponedDirectiveParse parse : postponed) { position = parse.position; parseSegment(parse.elem.getEnd(), parse.directive.getContentNodes(), child -> !elementsToSkip.contains(child)); if (parse.metadata.contentSetter != null) { parse.directive.setContentMethodName(parse.metadata.contentSetter.getName()); } else { if (!parse.metadata.ignoreContent && !isEmptyContent(parse.directive.getContentNodes())) { error(parse.elem, "Directive " + parse.metadata.cls.getName() + " should not have any content"); } parse.directive.getContentNodes().clear(); } } for (String varName : declaredVars.keySet()) { popVar(varName); } } static class PostponedDirectiveParse { int position; DirectiveMetadata metadata; DirectiveBinding directive; Element elem; List<PostponedAttributeParse> attributes = new ArrayList<>(); PostponedDirectiveParse(int position, DirectiveMetadata metadata, DirectiveBinding directive, Element elem) { this.position = position; this.metadata = metadata; this.directive = directive; this.elem = elem; } } static class PostponedAttributeParse { DirectiveAttributeMetadata meta; Attribute node; Expr<Void> expr; ValueType type; ValueType typeEstimate; GenericMethod sam; GenericMethod altSam; } private void validateNestedDirectives(DirectiveBinding directive, DirectiveMetadata metadata, Element elem, String prefix) { for (NestedDirective nestedMetadata : metadata.nestedDirectives) { NestedDirectiveBinding nestedDirective = findNestedDirectiveBinding(directive, nestedMetadata); String[] nameRules = nestedMetadata.metadata.nameRules; String name = nameRules.length == 1 ? nameRules[0] : "{" + Arrays.stream(nameRules).collect(Collectors.joining("|")) + "}"; if (nestedMetadata.required) { if (nestedDirective == null) { error(elem, "Nested directive " + prefix + ":" + name + " required but none encountered"); } } else if (!nestedMetadata.multiple) { if (nestedDirective.getDirectives().size() > 1) { error(elem, "Nested directive " + prefix + ":" + name + " should encounter only once"); } } } } private NestedDirectiveBinding getNestedDirectiveBinding(DirectiveBinding directive, NestedDirective metadata) { NestedDirectiveBinding binding = findNestedDirectiveBinding(directive, metadata); if (binding == null) { binding = new NestedDirectiveBinding(metadata.setter.getDescriber().getOwner().getName(), metadata.setter.getDescriber().getName(), metadata.metadata.cls.getName(), metadata.multiple); directive.getNestedDirectives().add(binding); } return binding; } private NestedDirectiveBinding findNestedDirectiveBinding(DirectiveBinding directive, NestedDirective metadata) { for (NestedDirectiveBinding binding : directive.getNestedDirectives()) { if (binding.getMethodName().equals(metadata.setter.getDescriber().getName()) && binding.getMethodOwner().equals(metadata.setter.getDescriber().getOwner().getName())) { return binding; } } return null; } private boolean isEmptyContent(List<TemplateNode> nodes) { for (TemplateNode node : nodes) { if (node instanceof DOMText) { DOMText text = (DOMText) node; if (!isEmptyText(text.getValue())) { return false; } } else { return false; } } return true; } private boolean isEmptyText(String text) { for (int i = 0; i < text.length(); ++i) { char c = text.charAt(i); if (!Character.isWhitespace(c) && c != '\r' && c != '\n' && c != '\t') { return false; } } return true; } private AttributeDirectiveBinding parseAttributeDirective(Attribute attr) { int prefixLength = attr.getName().indexOf(':'); String prefix = attr.getName().substring(0, prefixLength); String name = attr.getName().substring(prefixLength + 1); String fullName = prefix + ":" + name; AttributeDirectiveMetadata directiveMeta = resolveAttrDirective(prefix, name); if (directiveMeta == null) { error(attr.getNameSegment(), "Undefined directive " + fullName); return null; } AttributeDirectiveBinding directive = new AttributeDirectiveBinding(directiveMeta.cls.getName(), name); directive.setLocation(new Location(attr.getBegin(), attr.getEnd())); if (directiveMeta.nameSetter != null) { directive.setDirectiveNameMethodName(directiveMeta.nameSetter.getName()); } MethodDescriber getter = directiveMeta.getter; MethodDescriber setter = directiveMeta.setter; switch (directiveMeta.type) { case VARIABLE: { String varName = attr.getValue(); DirectiveVariableBinding varBinding = new DirectiveVariableBinding(setter.getOwner().getName(), getter.getName(), varName, getter.getRawReturnType(), directiveMeta.valueType); directive.getVariables().add(varBinding); break; } case FUNCTION: { Expr<Void> expr = parseExpr(attr.getValueSegment()); if (expr == null) { break; } TypedPlan plan = compileExpr(attr.getValueSegment(), expr, directiveMeta.sam.getActualOwner()); if (plan == null) { break; } DirectiveFunctionBinding functionBinding = new DirectiveFunctionBinding(setter.getOwner().getName(), setter.getName(), (LambdaPlan) plan.getPlan(), directiveMeta.sam.getDescriber().getOwner().getName()); directive.getFunctions().add(functionBinding); break; } case BIDIRECTIONAL: { diagnostics.add(new Diagnostic(attr.getBegin(), attr.getEnd(), "Bidirectional attributes " + "are not supported yet")); break; } } return directive; } private Expr<Void> parseExpr(Segment segment) { boolean hasErrors = false; org.teavm.flavour.expr.Parser exprParser = new org.teavm.flavour.expr.Parser(classResolver); Expr<Void> expr = exprParser.parse(segment.toString()); int offset = segment.getBegin(); for (Diagnostic diagnostic : exprParser.getDiagnostics()) { diagnostic = new Diagnostic(offset + diagnostic.getStart(), offset + diagnostic.getEnd(), diagnostic.getMessage()); diagnostics.add(diagnostic); hasErrors = true; } if (hasErrors) { return null; } return expr; } private TypedPlan compileExpr(Segment segment, Expr<?> expr, GenericClass type) { boolean hasErrors = false; Compiler compiler = new Compiler(classRepository, classResolver, new TemplateScope()); TypedPlan result = compiler.compileLambda(expr, type); PlanOffsetVisitor offsetVisitor = new PlanOffsetVisitor(segment.getBegin()); result.getPlan().acceptVisitor(offsetVisitor); int offset = segment.getBegin(); for (Diagnostic diagnostic : compiler.getDiagnostics()) { diagnostic = new Diagnostic(offset + diagnostic.getStart(), offset + diagnostic.getEnd(), diagnostic.getMessage()); diagnostics.add(diagnostic); hasErrors = true; } if (hasErrors) { return null; } return result; } private void pushVar(String name, ValueType type) { Deque<ValueType> stack = variables.get(name); if (stack == null) { stack = new ArrayDeque<>(); variables.put(name, stack); } stack.push(type); } private void popVar(String name) { Deque<ValueType> stack = variables.get(name); if (stack != null) { stack.pop(); if (stack.isEmpty()) { variables.remove(stack); } } } class TemplateScope implements Scope { @Override public ValueType variableType(String variableName) { Deque<ValueType> stack = variables.get(variableName); return stack != null && !stack.isEmpty() ? stack.peek() : null; } } private DirectiveMetadata resolveDirective(String prefix, String name) { String fullName = prefix + ":" + name; DirectiveMetadata directive = directives.get(fullName); if (directive == null) { List<DirectiveMetadata> byPrefix = avaliableDirectives.get(prefix); if (byPrefix != null) { directive: for (DirectiveMetadata testDirective : byPrefix) { for (String rule : testDirective.nameRules) { if (matchRule(rule, name)) { directive = testDirective; break directive; } } } } directives.put(fullName, directive); } return directive; } private NestedDirective resolveNestedDirective(DirectiveMetadata outer, String name) { for (NestedDirective nested : outer.nestedDirectives) { if (Arrays.stream(nested.metadata.nameRules).anyMatch(rule -> matchRule(rule, name))) { return nested; } } return null; } private AttributeDirectiveMetadata resolveAttrDirective(String prefix, String name) { String fullName = prefix + ":" + name; AttributeDirectiveMetadata directive = attrDirectives.get(fullName); if (directive == null) { List<AttributeDirectiveMetadata> byPrefix = avaliableAttrDirectives.get(prefix); if (byPrefix != null) { directive: for (AttributeDirectiveMetadata testDirective : byPrefix) { for (String rule : testDirective.nameRules) { if (matchRule(rule, name)) { directive = testDirective; break directive; } } } } attrDirectives.put(fullName, directive); } return directive; } private boolean matchRule(String rule, String name) { int index = rule.indexOf('*'); if (index < 0) { return name.equals(rule); } String prefix = rule.substring(0, index); String suffix = rule.substring(index + 1); return name.startsWith(prefix) && name.endsWith(suffix) && prefix.length() + suffix.length() < name.length(); } private void parseProcessingInstruction(StartTag tag) { if (tag.getName().equals("?import")) { parseImport(tag); } else if (tag.getName().equals("?use")) { parseUse(tag); } } private void parseImport(StartTag tag) { String importedName = normalizeQualifiedName(tag.getTagContent().toString()); if (importedName.endsWith(".*")) { classResolver.importPackage(importedName); } else { if (classResolver.findClass(importedName) == null) { error(tag.getTagContent(), "Class was not found: " + importedName); } else { classResolver.importClass(importedName); } } } private void parseUse(StartTag tag) { String content = tag.getTagContent().toString(); String[] parts = StringUtils.split(content, ":", 2); if (parts.length != 2) { error(tag.getTagContent(), "Illegal syntax for 'use' instruction"); return; } String prefix = parts[0].trim(); String packageName = normalizeQualifiedName(parts[1]); use(tag.getTagContent(), prefix, packageName); } private void use(Segment segment, String prefix, String packageName) { String resourceName = "META-INF/flavour/directive-packages/" + packageName; try (InputStream input = resourceProvider.openResource(resourceName)) { if (input == null) { error(segment, "Directive package was not found: " + packageName); return; } BufferedReader reader = new BufferedReader(new InputStreamReader(input)); List<DirectiveMetadata> directiveList = new ArrayList<>(); List<AttributeDirectiveMetadata> attributeDirectiveList = new ArrayList<>(); while (true) { String line = reader.readLine(); if (line == null) { break; } line = line.trim(); if (line.isEmpty() || line.startsWith("#")) { continue; } String className = packageName + "." + line; ClassDescriber cls = classRepository.describe(className); if (cls == null) { error(segment, "Class " + className + " declared by directive package was not found"); continue; } DirectiveParser directiveParser = new DirectiveParser(classRepository, diagnostics, segment); Object directiveMetadata = directiveParser.parse(cls); if (directiveMetadata instanceof DirectiveMetadata) { DirectiveMetadata elemDirectiveMeta = (DirectiveMetadata) directiveMetadata; directiveList.add(elemDirectiveMeta); } else if (directiveMetadata instanceof AttributeDirectiveMetadata) { AttributeDirectiveMetadata attrDirectiveMeta = (AttributeDirectiveMetadata) directiveMetadata; attributeDirectiveList.add(attrDirectiveMeta); } } avaliableDirectives.put(prefix, directiveList); avaliableAttrDirectives.put(prefix, attributeDirectiveList); } catch (IOException e) { throw new RuntimeException("IO exception occured parsing HTML input", e); } } private String normalizeQualifiedName(String text) { String[] parts = StringUtils.split(text.trim(), '.'); for (int i = 0; i < parts.length; ++i) { parts[i] = parts[i].trim(); } return StringUtils.join(parts, '.'); } private void error(Segment segment, String message) { diagnostics.add(new Diagnostic(segment.getBegin(), segment.getEnd(), message)); } }