From deb89f0e31926fa6638fc06a20a948dd7ca27201 Mon Sep 17 00:00:00 2001
From: Zach Levis
Date: Thu, 22 Oct 2020 20:36:23 -0700
Subject: [PATCH] yaml: update for core changes
---
.../configurate/yaml/ConfigurateScanner.java | 2 +-
.../configurate/yaml/ScalarStyle.java | 15 ++-
.../spongepowered/configurate/yaml/Tag.java | 68 ++++++++---
.../configurate/yaml/TagRepository.java | 16 ++-
.../configurate/yaml/Yaml11Tags.java | 108 ++++++++++++------
.../yaml/YamlConfigurationLoader.java | 15 +++
.../configurate/yaml/YamlParser.java | 52 ++++++---
.../configurate/yaml/YamlVisitor.java | 32 +++---
.../configurate/yaml/YamlTest.java | 16 +++
9 files changed, 238 insertions(+), 86 deletions(-)
diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/ConfigurateScanner.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/ConfigurateScanner.java
index bbfb7227c..371204b9c 100644
--- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/ConfigurateScanner.java
+++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/ConfigurateScanner.java
@@ -183,7 +183,7 @@ final class ConfigurateScanner implements Scanner { // Configurate: rename + pac
// 32-bit Unicode (Supplementary characters are supported)
ESCAPE_CODES.put(Character.valueOf('U'), 8);
}
- private final StreamReader reader;
+ final StreamReader reader; // Configurate: private -> package-private
// Had we reached the end of the stream?
private boolean done = false;
diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/ScalarStyle.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/ScalarStyle.java
index a6be0788f..e1b5e47b0 100644
--- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/ScalarStyle.java
+++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/ScalarStyle.java
@@ -19,7 +19,7 @@
import org.checkerframework.checker.nullness.qual.Nullable;
import org.yaml.snakeyaml.DumperOptions;
-import java.util.HashMap;
+import java.util.EnumMap;
import java.util.Map;
/**
@@ -27,14 +27,25 @@
*/
public enum ScalarStyle {
+ /**
+ * A double-quoted string.
+ *
+ * "hello world"
+ */
DOUBLE_QUOTED(DumperOptions.ScalarStyle.DOUBLE_QUOTED),
+
+ /**
+ * A single-quoted string.
+ *
+ * 'hello world'
+ */
SINGLE_QUOTED(DumperOptions.ScalarStyle.SINGLE_QUOTED),
UNQUOTED(DumperOptions.ScalarStyle.PLAIN),
FOLDED(DumperOptions.ScalarStyle.FOLDED),
LITERAL(DumperOptions.ScalarStyle.LITERAL)
;
- private static final Map BY_SNAKE = new HashMap<>();
+ private static final Map BY_SNAKE = new EnumMap<>(DumperOptions.ScalarStyle.class);
private final DumperOptions.ScalarStyle snake;
ScalarStyle(final DumperOptions.ScalarStyle snake) {
diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/Tag.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/Tag.java
index 9d1e82426..ae77ade01 100644
--- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/Tag.java
+++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/Tag.java
@@ -31,6 +31,11 @@
@AutoValue
public abstract class Tag {
+ /**
+ * Create a new builder for a {@link Tag}.
+ *
+ * @return a new builder
+ */
public static Tag.Builder builder() {
return new AutoValue_Tag.Builder();
}
@@ -43,14 +48,14 @@ public static Tag.Builder builder() {
*
* @return tag uri, with `tag:` schema
*/
- public abstract URI getUri();
+ public abstract URI uri();
/**
* The native type that maps to this tag.
*
* @return native type for tag
*/
- public abstract Type getNativeType();
+ public abstract Type nativeType();
/**
* Pattern to test scalar values against when resolving this tag.
@@ -58,38 +63,73 @@ public static Tag.Builder builder() {
* @return match pattern
* @apiNote See ยง3.3.2 of YAML 1.1 spec
*/
- public abstract Pattern getTargetPattern();
+ public abstract Pattern targetPattern();
/**
* Whether this tag is a global tag with a full namespace or a local one.
*
* @return if this is a global tag
*/
- public final boolean isGlobal() {
- return getUri().getScheme().equals("tag");
+ public final boolean global() {
+ return uri().getScheme().equals("tag");
}
+ /**
+ * A builder for {@link Tag Tags}.
+ */
@AutoValue.Builder
public abstract static class Builder {
- public abstract Builder setUri(URI url);
-
- public final Builder setUri(final String tagUrl) {
+ /**
+ * Set the URI used to refer to the tag.
+ *
+ * @param url canonical tag URI
+ * @return this builder
+ */
+ public abstract Builder uri(URI url);
+
+ /**
+ * Set the URI used to refer to the tag, parsing a new URL from
+ * the argument.
+ *
+ * @param tagUrl canonical tag URI
+ * @return this builder
+ */
+ public final Builder uri(final String tagUrl) {
try {
if (tagUrl.startsWith("!")) {
- return this.setUri(new URI(tagUrl.substring(1)));
+ return this.uri(new URI(tagUrl.substring(1)));
} else {
- return this.setUri(new URI(tagUrl));
+ return this.uri(new URI(tagUrl));
}
} catch (final URISyntaxException e) {
throw new RuntimeException(e);
}
}
- public abstract Builder setNativeType(Type type);
-
- public abstract Builder setTargetPattern(Pattern targetPattern);
-
+ /**
+ * The Java type that will be used to represent this value in the node
+ * structure.
+ *
+ * @param type type for the value
+ * @return this builder
+ */
+ public abstract Builder nativeType(Type type);
+
+ /**
+ * Pattern to match an undefined scalar string to this tag as an
+ * implicit tag.
+ *
+ * @param targetPattern pattern to match
+ * @return this builder
+ */
+ public abstract Builder targetPattern(Pattern targetPattern);
+
+ /**
+ * Create a new tag from the provided parameters.
+ *
+ * @return a new tag
+ */
public abstract Tag build();
}
diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/TagRepository.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/TagRepository.java
index 148f6d9a7..724f07c6e 100644
--- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/TagRepository.java
+++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/TagRepository.java
@@ -24,13 +24,15 @@
import java.util.List;
import java.util.Map;
+/**
+ * A collection of tags that are understood when reading a document.
+ */
public final class TagRepository {
private final List tags;
private final Map, Tag> byErasedType;
private final Map byName;
-
/**
* Create a new tag repository.
*
@@ -45,19 +47,25 @@ public static TagRepository of(final List tags) {
this.tags = tags;
this.byErasedType = UnmodifiableCollections.buildMap(map -> {
for (final Tag tag : this.tags) {
- map.put(erase(tag.getNativeType()), tag);
+ map.put(erase(tag.nativeType()), tag);
}
});
this.byName = UnmodifiableCollections.buildMap(map -> {
for (final Tag tag : this.tags) {
- map.put(tag.getUri().toString(), tag);
+ map.put(tag.uri().toString(), tag);
}
});
}
+ /**
+ * Determine the implicit tag for a scalar value.
+ *
+ * @param scalar scalar to test
+ * @return the first matching tag
+ */
public @Nullable Tag forInput(final String scalar) {
for (final Tag tag : this.tags) {
- if (tag.getTargetPattern().matcher(scalar).matches()) {
+ if (tag.targetPattern().matcher(scalar).matches()) {
return tag;
}
}
diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/Yaml11Tags.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/Yaml11Tags.java
index 522a4f18e..4dee93a03 100644
--- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/Yaml11Tags.java
+++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/Yaml11Tags.java
@@ -34,73 +34,113 @@ private static String yamlOrg(final String specific) {
return "tag:yaml.org,2002:" + specific;
}
- // https://yaml.org/type/binary.html
+ /**
+ * A binary data tag.
+ *
+ * @see tag:yaml.org,2002:binary
+ */
public static final Tag BINARY = Tag.builder()
- .setUri(yamlOrg("binary"))
- .setNativeType(byte[].class)
- .setTargetPattern(Pattern.compile("base64 TODO"))
+ .uri(yamlOrg("binary"))
+ .nativeType(byte[].class)
+ .targetPattern(Pattern.compile("base64 TODO"))
.build();
- // https://yaml.org/type/bool.html
- // Canonically these are y|n in YAML 1.1, but because YAML 1.2 moves to
- // true|false only, we'll just use those
+ /**
+ * A boolean value.
+ *
+ * @implNote Canonically, these are y|n in YAML 1.1, but because YAML 1.2
+ * will only support true|false, we will treat those as the default
+ * output format.
+ * @see tag:yaml.org,2002:bool
+ */
public static final Tag BOOL = Tag.builder()
- .setUri(yamlOrg("bool"))
- .setNativeType(Boolean.class)
- .setTargetPattern(Pattern.compile("y|Y|yes|Yes|YES|n|N|no|No|NO"
+ .uri(yamlOrg("bool"))
+ .nativeType(Boolean.class)
+ .targetPattern(Pattern.compile("y|Y|yes|Yes|YES|n|N|no|No|NO"
+ "|true|True|TRUE|false|False|FALSE"
+ "|on|On|ON|off|Off|OFF"))
.build();
- // https://yaml.org/type/float.html
+ /**
+ * A floating-point number.
+ *
+ * @see tag:yaml.org,2002:float
+ */
public static final Tag FLOAT = Tag.builder()
- .setUri(yamlOrg("float"))
- .setNativeType(Double.class)
- .setTargetPattern(Pattern.compile("[-+]?([0-9][0-9_]*)?\\.[0-9.]*([eE][-+][0-9]+)?" // base 10
+ .uri(yamlOrg("float"))
+ .nativeType(Double.class)
+ .targetPattern(Pattern.compile("[-+]?([0-9][0-9_]*)?\\.[0-9.]*([eE][-+][0-9]+)?" // base 10
+ "|[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+\\.[0-9]*" // base 60
+ "|[-+]?\\.(inf|Inf|INF)" // infinity
+ "|\\.(nan|NaN|NAN)")) // not a number
.build();
- // https://yaml.org/type/int.html
+ /**
+ * An integer.
+ *
+ * @see tag:yaml.org,2002:int
+ */
public static final Tag INT = Tag.builder()
- .setUri(yamlOrg("int"))
- .setNativeType(Long.class)
- .setTargetPattern(Pattern.compile("[-+]?0b[0-1_]+" // base 2
+ .uri(yamlOrg("int"))
+ .nativeType(Long.class)
+ .targetPattern(Pattern.compile("[-+]?0b[0-1_]+" // base 2
+ "|[-+]?0[0-7_]+" // base 8
+ "|[-+]?(0|[1-9][0-9_]*)" // base 10
+ "|[-+]?0x[0-9a-fA-F_]+" // base 16
+ "|[-+]?[1-9][0-9_]*(:[0-5]?[0-9])+")) // base 60
.build();
- // https://yaml.org/type/merge.html
+ /**
+ * A mapping merge.
+ *
+ * This will not be supported in Configurate until reference-type nodes
+ * are fully implemented.
+ *
+ * @see tag:yaml.org,2002:merge
+ */
public static final Tag MERGE = Tag.builder()
- .setUri(yamlOrg("merge"))
- .setNativeType(ConfigurationNode.class)
- .setTargetPattern(Pattern.compile("<<"))
+ .uri(yamlOrg("merge"))
+ .nativeType(ConfigurationNode.class)
+ .targetPattern(Pattern.compile("<<"))
.build();
- // https://yaml.org/type/null.html
+ /**
+ * The value {@code null}.
+ *
+ * Because Configurate has no distinction between a node with a
+ * {@code null} value, and a node that does not exist, this tag will most
+ * likely never be encountered in an in-memory representation.
+ *
+ * @see tag:yaml.org,2002:null
+ */
public static final Tag NULL = Tag.builder()
- .setUri(yamlOrg("null"))
- .setNativeType(Void.class)
- .setTargetPattern(Pattern.compile("~"
+ .uri(yamlOrg("null"))
+ .nativeType(Void.class)
+ .targetPattern(Pattern.compile("~"
+ "|null|Null|NULL"
+ "|$"))
.build();
- // https://yaml.org/type/str.html
+ /**
+ * Any string.
+ *
+ * @see tag:yaml.org,2002:str
+ */
public static final Tag STR = Tag.builder()
- .setUri(yamlOrg("str"))
- .setNativeType(String.class)
- .setTargetPattern(Pattern.compile(".+")) // empty scalar is NULL
+ .uri(yamlOrg("str"))
+ .nativeType(String.class)
+ .targetPattern(Pattern.compile(".+")) // empty scalar is NULL
.build();
- // https://yaml.org/type/timestamp.html
+ /**
+ * A timestamp, containing date, time, and timezone.
+ *
+ * @see tag:yaml.org,2002:timestamp
+ */
public static final Tag TIMESTAMP = Tag.builder()
- .setUri(yamlOrg("timestamp"))
- .setNativeType(ZonedDateTime.class)
- .setTargetPattern(Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}" // YYYY-MM-DD
+ .uri(yamlOrg("timestamp"))
+ .nativeType(ZonedDateTime.class)
+ .targetPattern(Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}" // YYYY-MM-DD
+ "|[0-9]{4}" // YYYY
+ "-[0-9]{1,2}" // month
+ "-[0-9]{1,2}" // day
diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java
index 8072802f1..343c73f69 100644
--- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java
+++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java
@@ -48,10 +48,25 @@
*/
public final class YamlConfigurationLoader extends AbstractConfigurationLoader {
+ /**
+ * The identifier for a YAML anchor that can be used to refer to the node
+ * this hint is set on.
+ */
public static final RepresentationHint ANCHOR_ID = RepresentationHint.of("anchor-id", String.class);
+ /**
+ * The YAML scalar style this node should attempt to use.
+ *
+ * If the chosen scalar style would produce syntactically invalid YAML, a
+ * valid one will replace it.
+ */
public static final RepresentationHint SCALAR_STYLE = RepresentationHint.of("scalar-style", ScalarStyle.class);
+ /**
+ * The YAML node style to use for collection nodes. A {@code null} value
+ * will instruct the emitter to fall back to the
+ * {@link Builder#nodeStyle()} setting.
+ */
public static final RepresentationHint NODE_STYLE = RepresentationHint.of("node-style", NodeStyle.class);
/**
diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlParser.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlParser.java
index 273adcad7..74a7b9199 100644
--- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlParser.java
+++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlParser.java
@@ -16,18 +16,21 @@
*/
package org.spongepowered.configurate.yaml;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.BasicConfigurationNode;
import org.spongepowered.configurate.CommentedConfigurationNodeIntermediary;
+import org.spongepowered.configurate.ConfigurateException;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.ConfigurationNodeFactory;
+import org.spongepowered.configurate.loader.ParsingException;
import org.yaml.snakeyaml.events.AliasEvent;
import org.yaml.snakeyaml.events.CollectionStartEvent;
import org.yaml.snakeyaml.events.Event;
import org.yaml.snakeyaml.events.NodeEvent;
import org.yaml.snakeyaml.events.ScalarEvent;
import org.yaml.snakeyaml.parser.ParserImpl;
+import org.yaml.snakeyaml.reader.StreamReader;
-import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
@@ -62,28 +65,28 @@ private ConfigurateScanner scanner() {
return (ConfigurateScanner) this.scanner;
}
- Event requireEvent(final Event.ID type) throws IOException {
+ Event requireEvent(final Event.ID type) throws ParsingException {
final Event next = peekEvent();
if (!next.is(type)) {
- throw new IOException("Expected next event of type" + type + " but was " + next.getEventId());
+ throw makeError("Expected next event of type" + type + " but was " + next.getEventId(), null);
}
return this.getEvent();
}
@SuppressWarnings("unchecked")
- T requireEvent(final Event.ID type, final Class clazz) throws IOException {
+ T requireEvent(final Event.ID type, final Class clazz) throws ParsingException {
final Event next = peekEvent();
if (!next.is(type)) {
- throw new IOException("Expected next event of type" + type + " but was " + next.getEventId());
+ throw makeError("Expected next event of type" + type + " but was " + next.getEventId(), null);
}
if (!clazz.isInstance(next)) {
- throw new IOException("Expected event of type " + clazz + " but got a " + next.getClass());
+ throw makeError("Expected event of type " + clazz + " but got a " + next.getClass(), null);
}
return (T) this.getEvent();
}
- public Stream stream(final ConfigurationNodeFactory factory) throws IOException {
+ public Stream stream(final ConfigurationNodeFactory factory) throws ParsingException {
requireEvent(Event.ID.StreamStart);
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator() {
@Override
@@ -100,31 +103,34 @@ public N next() {
final N node = factory.createNode();
document(node);
return node;
- } catch (final IOException e) {
+ } catch (final ConfigurateException e) {
throw new RuntimeException(e); // TODO
}
}
}, Spliterator.IMMUTABLE | Spliterator.ORDERED | Spliterator.NONNULL), false);
}
- public void singleDocumentStream(final ConfigurationNode node) throws IOException {
+ public void singleDocumentStream(final ConfigurationNode node) throws ParsingException {
requireEvent(Event.ID.StreamStart);
document(node);
requireEvent(Event.ID.StreamEnd);
}
- public void document(final ConfigurationNode node) throws IOException {
+ public void document(final ConfigurationNode node) throws ParsingException {
requireEvent(Event.ID.DocumentStart);
this.scanner().setCaptureComments(node instanceof CommentedConfigurationNodeIntermediary>);
try {
value(node);
+ } catch (final ConfigurateException ex) {
+ ex.initPath(node::path);
+ throw ex;
} finally {
this.aliases.clear();
}
requireEvent(Event.ID.DocumentEnd);
}
- void value(final ConfigurationNode node) throws IOException {
+ void value(final ConfigurationNode node) throws ParsingException {
// We have to capture the comment before we peek ahead
// peeking ahead will start consuming the next event and its comments
applyComment(node);
@@ -157,17 +163,17 @@ void value(final ConfigurationNode node) throws IOException {
alias(node);
break;
default:
- throw new IOException("Unexpected event type " + peekEvent().getEventId());
+ throw makeError(node, "Unexpected event type " + peekEvent().getEventId(), null);
}
}
- void scalar(final ConfigurationNode node) throws IOException {
+ void scalar(final ConfigurationNode node) throws ParsingException {
final ScalarEvent scalar = requireEvent(Event.ID.Scalar, ScalarEvent.class);
node.hint(YamlConfigurationLoader.SCALAR_STYLE, ScalarStyle.fromSnakeYaml(scalar.getScalarStyle()));
node.raw(scalar.getValue()); // TODO: tags and value types
}
- void mapping(final ConfigurationNode node) throws IOException {
+ void mapping(final ConfigurationNode node) throws ParsingException {
requireEvent(Event.ID.MappingStart);
node.raw(Collections.emptyMap());
@@ -181,7 +187,7 @@ void mapping(final ConfigurationNode node) throws IOException {
}
final ConfigurationNode child = node.node(keyHolder.raw());
if (!child.virtual()) { // duplicate keys are forbidden (3.2.1.3)
- throw new IOException("Duplicate key '" + child.key() + "' encountered!");
+ throw makeError(node, "Duplicate key '" + child.key() + "' encountered!", null);
}
value(node.node(child));
}
@@ -189,7 +195,7 @@ void mapping(final ConfigurationNode node) throws IOException {
requireEvent(Event.ID.MappingEnd);
}
- void sequence(final ConfigurationNode node) throws IOException {
+ void sequence(final ConfigurationNode node) throws ParsingException {
requireEvent(Event.ID.SequenceStart);
node.raw(Collections.emptyList());
@@ -200,14 +206,24 @@ void sequence(final ConfigurationNode node) throws IOException {
requireEvent(Event.ID.SequenceEnd);
}
- void alias(final ConfigurationNode node) throws IOException {
+ void alias(final ConfigurationNode node) throws ParsingException {
final AliasEvent event = requireEvent(Event.ID.Alias, AliasEvent.class);
final ConfigurationNode target = this.aliases.get(event.getAnchor());
if (target == null) {
- throw new IOException("Unknown anchor '" + event.getAnchor() + "'");
+ throw makeError(node, "Unknown anchor '" + event.getAnchor() + "'", null);
}
node.from(target); // TODO: Reference node types
node.hint(YamlConfigurationLoader.ANCHOR_ID, null); // don't duplicate alias
}
+ private ParsingException makeError(final @Nullable String message, final @Nullable Throwable error) {
+ final StreamReader reader = scanner().reader;
+ return new ParsingException(reader.getLine(), reader.getColumn(), null, message, error);
+ }
+
+ private ParsingException makeError(final ConfigurationNode node, final @Nullable String message, final @Nullable Throwable error) {
+ final StreamReader reader = scanner().reader;
+ return new ParsingException(node, reader.getLine(), reader.getColumn(), null, message, error);
+ }
+
}
diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlVisitor.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlVisitor.java
index 29535bb3c..68ad6c82f 100644
--- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlVisitor.java
+++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlVisitor.java
@@ -17,10 +17,12 @@
package org.spongepowered.configurate.yaml;
import org.checkerframework.checker.nullness.qual.Nullable;
+import org.spongepowered.configurate.ConfigurateException;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.ConfigurationVisitor;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.emitter.Emitter;
+import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.events.DocumentEndEvent;
import org.yaml.snakeyaml.events.DocumentStartEvent;
import org.yaml.snakeyaml.events.Event;
@@ -39,7 +41,7 @@
import java.io.IOException;
-final class YamlVisitor implements ConfigurationVisitor {
+final class YamlVisitor implements ConfigurationVisitor {
private static final StreamStartEvent STREAM_START = new StreamStartEvent(null, null);
private static final StreamEndEvent STREAM_END = new StreamEndEvent(null, null);
@@ -58,12 +60,12 @@ final class YamlVisitor implements ConfigurationVisitor