package tc.oc.pgm.points; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; import org.bukkit.util.Vector; import org.jdom2.Attribute; import org.jdom2.Element; import tc.oc.pgm.features.FeatureDefinitionContext; import tc.oc.pgm.map.inject.MapScoped; import tc.oc.pgm.regions.PointRegion; import tc.oc.pgm.regions.RandomPointsValidation; import tc.oc.pgm.regions.Region; import tc.oc.pgm.regions.RegionParser; import tc.oc.pgm.regions.Union; import tc.oc.pgm.utils.XMLUtils; import tc.oc.pgm.xml.InvalidXMLException; import tc.oc.pgm.xml.Node; import static tc.oc.commons.core.exception.LambdaExceptionUtils.rethrowConsumer; /** * PointProvider grammar is a bit strange due to backward compatibility. The root element is what * the caller passes to {@link #parse}, and it can have any name at all. This element can either * be a container of PointProvider sub-elements, or a {@link PointRegion}. Child elements are * parsed as {@link Region}s and wrapped in {@link RegionPointProvider}s, EXCEPT for <point>s, * which are treated the same as the root element. */ @MapScoped public class PointParser { private final FeatureDefinitionContext features; private final RegionParser regionParser; @Inject private PointParser(FeatureDefinitionContext features, RegionParser regionParser) { this.features = features; this.regionParser = regionParser; } private Region validate(Region region, Node node) throws InvalidXMLException { features.validate(region, node, (Region region0, Node node0) -> Union.expand(region0) .forEach(rethrowConsumer(sub -> RandomPointsValidation.INSTANCE.validate(sub, features.sourceNode(sub)))) ); return region; } /** * Parse any number of {@link PointProvider}s in attributes or children of the given names. */ public List<PointProvider> parseMultiProperty(Element el, PointProviderAttributes parentAttributes, String name, String... aliases) throws InvalidXMLException { final PointProviderAttributes attributes = parseAttributes(el, parentAttributes); List<PointProvider> providers = new ArrayList<>(); Node.attributes(el, name, aliases).forEach(rethrowConsumer(attr -> { providers.add(new RegionPointProvider(validate(regionParser.parseReference(attr), attr), attributes)); })); Node.elements(el, name, aliases).forEach(rethrowConsumer(child -> { providers.add(new RegionPointProvider(validate(regionParser.parseChild(child.asElement()), child), parseAttributes(child.asElement(), attributes))); })); return providers; } /** * Parse the given element as a container for {@link PointProvider}s. The given element is not itself * parsed as a PointProvider, but its attributes are inherited by any contained PointProviders. */ public List<PointProvider> parseChildren(Element el, PointProviderAttributes attributes) throws InvalidXMLException { return parseChildren(new ArrayList<>(), el, attributes); } /** * Parse the given element as a {@link PointProvider} or container of PointProviders. */ public List<PointProvider> parse(Element el, PointProviderAttributes attributes) throws InvalidXMLException { return parsePoint(new ArrayList<>(), el, attributes); } private List<PointProvider> parsePoint(List<PointProvider> providers, Element el, PointProviderAttributes attributes) throws InvalidXMLException { attributes = parseAttributes(el, attributes); if(el.getChildren().isEmpty()) { // If it has no children, parse it as a Point region (regardless of the element name) providers.add(new RegionPointProvider(new PointRegion(XMLUtils.parseVector(Node.of(el))), attributes)); } else { // If it does have children, parse it as a container parseChildren(providers, el, attributes); } return providers; } private List<PointProvider> parseChildren(List<PointProvider> providers, Element el, PointProviderAttributes attributes) throws InvalidXMLException { attributes = parseAttributes(el, attributes); for(Element elChild : el.getChildren()) { parseChild(providers, elChild, attributes); } return providers; } private List<PointProvider> parseChild(List<PointProvider> providers, Element el, PointProviderAttributes attributes) throws InvalidXMLException { attributes = parseAttributes(el, attributes); if("point".equals(el.getName())) { // For legacy compatibility, <point> is treated specially parsePoint(providers, el, attributes); } else { // Anything else is parsed as a region parseRegion(providers, el, attributes); } return providers; } private void parseRegion(List<PointProvider> providers, Element el, PointProviderAttributes attributes) throws InvalidXMLException { providers.add(new RegionPointProvider(validate(regionParser.parseElement(el), new Node(el)), attributes)); } AngleProvider parseStaticAngleProvider(Attribute attr) throws InvalidXMLException { Float angle = XMLUtils.parseNumber(attr, Float.class, (Float) null); return angle == null ? null : new StaticAngleProvider(angle); } public PointProviderAttributes parseAttributes(Element el, PointProviderAttributes attributes) throws InvalidXMLException { boolean safe = XMLUtils.parseBoolean(el.getAttribute("safe"), attributes.isSafe()); boolean outdoors = XMLUtils.parseBoolean(el.getAttribute("outdoors"), attributes.isOutdoors()); Vector target = XMLUtils.parseVector(el.getAttribute("angle"), (Vector) null); if(target != null) { return new PointProviderAttributes(new DirectedYawProvider(target), new DirectedPitchProvider(target), safe, outdoors); } AngleProvider yawProvider = parseStaticAngleProvider(el.getAttribute("yaw")); AngleProvider pitchProvider = parseStaticAngleProvider(el.getAttribute("pitch")); if(yawProvider != null || pitchProvider != null || safe != attributes.isSafe()) { return new PointProviderAttributes(yawProvider, pitchProvider, safe, outdoors); } return attributes; } }