diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..7b016a89f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.compile.nullAnalysis.mode": "automatic" +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index a4ee67662..d0dff64d9 100644 --- a/pom.xml +++ b/pom.xml @@ -167,7 +167,7 @@ junit junit - ${junit.version} + 4.12 test @@ -243,6 +243,9 @@ org.apache.maven.plugins maven-checkstyle-plugin 2.17 + + true + validate @@ -395,7 +398,7 @@ v@{project.version} true + – the releases profile – to become active during the Release process. --> releases @@ -561,4 +564,4 @@ - + \ No newline at end of file diff --git a/wrangler-api/src/main/java/io/cdap/wrangler/api/TokenGroup.java b/wrangler-api/src/main/java/io/cdap/wrangler/api/TokenGroup.java index c5a888e48..d4d2d1002 100644 --- a/wrangler-api/src/main/java/io/cdap/wrangler/api/TokenGroup.java +++ b/wrangler-api/src/main/java/io/cdap/wrangler/api/TokenGroup.java @@ -22,6 +22,8 @@ import java.util.Iterator; import java.util.List; + + /** * Class description here. */ diff --git a/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/ByteSize.java b/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/ByteSize.java new file mode 100644 index 000000000..1acd5d330 --- /dev/null +++ b/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/ByteSize.java @@ -0,0 +1,119 @@ +/* + * Copyright © 2017-2019 Cask Data, Inc. + * + * 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 io.cdap.wrangler.api.parser; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import io.cdap.wrangler.api.annotations.PublicEvolving; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Represents a ByteSize object that can parse and store byte size values + * in different units such as KB, MB, GB, or TB. + */ +@PublicEvolving +public class ByteSize implements Token { + private long bytes; + + // Regular expression pattern to match byte sizes with optional units (KB, MB, + // GB, TB) + private static final Pattern BYTE_SIZE_PATTERN = Pattern.compile("^\\s*(\\d+)\\s*(KB|MB|GB|TB)?\\s*$", + Pattern.CASE_INSENSITIVE); + + private static final int KILOBYTE = 1024; + private static final int MEGABYTE = KILOBYTE * KILOBYTE; + private static final int GIGABYTE = KILOBYTE * MEGABYTE; + private static final int TERABYTE = KILOBYTE * GIGABYTE; + + /** + * Constructor to initialize the ByteSize object by parsing the input token + * string. + * + * @param token The token representing byte size (e.g., "10KB", "100MB"). + * @throws IllegalArgumentException if the token does not match the expected + * format. + */ + public ByteSize(String token) { + Matcher matcher = BYTE_SIZE_PATTERN.matcher(token); + if (matcher.matches()) { + long value = Long.parseLong(matcher.group(1)); + String unit = matcher.group(2); + switch (unit == null ? "" : unit.toUpperCase()) { + case "KB": + this.bytes = value * KILOBYTE; + break; + case "MB": + this.bytes = value * MEGABYTE; + break; + case "GB": + this.bytes = value * GIGABYTE; + break; + case "TB": + this.bytes = value * TERABYTE; + break; + default: + this.bytes = value; // bytes if no unit is specified + } + } else { + throw new IllegalArgumentException("Invalid ByteSize format: " + token); + } + } + + /** + * Returns the value of the byte size. + * + * @return The byte size as a Long object. + */ + @Override + public Long value() { + return Long.valueOf(bytes); // Return as Long object to match the Token interface + } + + /** + * Returns the type of the token (ByteSize). + * + * @return The TokenType for ByteSize. + */ + @Override + public TokenType type() { + return TokenType.BYTE_SIZE; + } + + /** + * Converts the ByteSize object to its JSON representation. + * + * @return The JSON representation of the ByteSize object. + */ + @Override + public JsonElement toJson() { + JsonObject object = new JsonObject(); + object.addProperty("type", TokenType.BYTE_SIZE.name()); + object.addProperty("value", bytes); + return object; + } + + /** + * Gets the byte size in bytes. + * + * @return The byte size in bytes. + */ + public long getBytes() { + return bytes; + } +} + diff --git a/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/TimeDuration.java b/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/TimeDuration.java new file mode 100644 index 000000000..3342a278d --- /dev/null +++ b/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/TimeDuration.java @@ -0,0 +1,147 @@ +/* + * Copyright © 2017-2019 Cask Data, Inc. + * + * 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 io.cdap.wrangler.api.parser; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import io.cdap.wrangler.api.annotations.PublicEvolving; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Represents a time duration in milliseconds, which can be parsed from a string + * token + * like "10s" (10 seconds), "5m" (5 minutes), "3h" (3 hours), or "1d" (1 day). + * The class supports parsing and conversion of these tokens into milliseconds. + * + *

+ * The token format can either be in the form of a number followed by an + * optional unit + * (e.g., "10s", "5m", "3h", "1d"), or a number without a unit, in which case it + * is treated + * as milliseconds (e.g., "500" represents 500 milliseconds). + *

+ * + *

+ * The supported time units are: ms (milliseconds), s (seconds), m (minutes), h + * (hours), + * and d (days). If no unit is provided, milliseconds is assumed by default. + *

+ * + * @see Token + */ +@PublicEvolving +public class TimeDuration implements Token { + private long millis; + + // Regular expression to match time duration formats like "10s", "5m", "3h", + // "1d" + private static final Pattern TIME_DURATION_PATTERN = Pattern.compile("^(\\d+)(ms|s|m|h|d)?$", + Pattern.CASE_INSENSITIVE); + + // Constant values for time multipliers + private static final int MS_IN_SECOND = 1000; + private static final int MS_IN_MINUTE = MS_IN_SECOND * 60; + private static final int MS_IN_HOUR = MS_IN_MINUTE * 60; + private static final int MS_IN_DAY = MS_IN_HOUR * 24; + + /** + * Constructs a TimeDuration object by parsing the provided token string. + * + * The token can represent a time duration, such as "10s" for 10 seconds, "5m" + * for + * 5 minutes, "3h" for 3 hours, or "1d" for 1 day. If no unit is specified, it + * defaults + * to milliseconds. The valid units are "ms", "s", "m", "h", and "d". + * + * @param token The token representing the time duration, e.g., "10s", "5m", + * "3h". + * @throws IllegalArgumentException if the token does not match the expected + * format. + */ + public TimeDuration(final String token) { + Matcher matcher = TIME_DURATION_PATTERN.matcher(token); + if (matcher.matches()) { + long value = Long.parseLong(matcher.group(1)); + String unit = matcher.group(2); + switch (unit == null ? "" : unit.toLowerCase()) { + case "ms": + this.millis = value; + break; + case "s": + this.millis = value * MS_IN_SECOND; + break; + case "m": + this.millis = value * MS_IN_MINUTE; + break; + case "h": + this.millis = value * MS_IN_HOUR; + break; + case "d": + this.millis = value * MS_IN_DAY; + break; + default: + throw new IllegalArgumentException("Invalid TimeDuration format"); + } + } else { + throw new IllegalArgumentException("Invalid TimeDuration format: " + token); + } + } + + /** + * Returns the value of the time duration in milliseconds. + * + * @return The time duration in milliseconds as a Long. + */ + @Override + public Long value() { + return Long.valueOf(millis); // Return as Long object to match the Token interface + } + + /** + * Returns the type of the token (TimeDuration). + * + * @return The TokenType for TimeDuration. + */ + @Override + public TokenType type() { + return TokenType.TIME_DURATION; + } + + /** + * Converts the TimeDuration object to its JSON representation. + * + * @return The JSON representation of the TimeDuration object. + */ + @Override + public JsonElement toJson() { + JsonObject object = new JsonObject(); + object.addProperty("type", TokenType.TIME_DURATION.name()); + object.addProperty("value", millis); + return object; + } + + /** + * Gets the time duration in milliseconds. + * + * @return The time duration in milliseconds. + */ + public long getMilliseconds() { + return millis; + } +} + diff --git a/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/TokenDefinition.java b/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/TokenDefinition.java index 25bf2d10b..b1491706a 100644 --- a/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/TokenDefinition.java +++ b/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/TokenDefinition.java @@ -21,22 +21,32 @@ import java.io.Serializable; /** - * The TokenDefinition class represents a definition of token as specified - * by the user while defining a directive usage. All definitions of a token are represented + * The TokenDefinition class represents a definition of token as + * specified + * by the user while defining a directive usage. All definitions of a token are + * represented * by a instance of this class. * - * The definition are constant (immutable) and they cannot be changed once defined. + * The definition are constant (immutable) and they cannot be changed once + * defined. * For example : * * TokenDefinition token = new TokenDefintion("column", TokenType.COLUMN_NAME, null, 0, Optional.FALSE); * * - *

The class TokenDefinition includes methods for retrieveing different members of - * like name of the token, type of the token, label associated with token, whether it's optional or not - * and the ordinal number of the token in the TokenGroup.

+ *

+ * The class TokenDefinition includes methods for retrieveing + * different members of + * like name of the token, type of the token, label associated with token, + * whether it's optional or not + * and the ordinal number of the token in the TokenGroup. + *

* - *

As this class is immutable, the constructor requires all the member variables to be presnted - * for an instance of this object to be created.

+ *

+ * As this class is immutable, the constructor requires all the member variables + * to be presnted + * for an instance of this object to be created. + *

*/ @PublicEvolving public final class TokenDefinition implements Serializable { @@ -55,23 +65,27 @@ public TokenDefinition(String name, TokenType type, String label, int ordinal, b } /** - * @return Label associated with the token. Label provides a way to override the usage description - * for this TokenDefinition. If a label is not provided, then this return null. + * @return Label associated with the token. Label provides a way to override the + * usage description + * for this TokenDefinition. If a label is not provided, + * then this return null. */ public String label() { return label; } /** - * @return Returns the oridinal number of this TokenDefinition within - * the TokenGroup, + * @return Returns the oridinal number of this TokenDefinition + * within + * the TokenGroup, */ public int ordinal() { return ordinal; } /** - * @return true, if this TokenDefinition is optional, false otherwise. + * @return true, if this TokenDefinition is optional, false + * otherwise. */ public boolean optional() { return optional; diff --git a/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/TokenType.java b/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/TokenType.java index 8c93b0e6a..b1fbecfa7 100644 --- a/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/TokenType.java +++ b/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/TokenType.java @@ -14,6 +14,7 @@ * the License. */ + package io.cdap.wrangler.api.parser; import io.cdap.wrangler.api.annotations.PublicEvolving; @@ -24,7 +25,7 @@ * The TokenType class provides the enumerated types for different types of * tokens that are supported by the grammar. * - * Each of the enumerated types specified in this class also has associated + * Each of the enumerated types specified in this class also has an associated * object representing it. e.g. {@code DIRECTIVE_NAME} is represented by the * object {@code DirectiveName}. * @@ -52,7 +53,7 @@ public enum TokenType implements Serializable { /** * Represents the enumerated type for the object of {@code ColumnName} type. - * This type is associated with token that represents the column as defined + * This type is associated with the token that represents the column as defined * by the grammar as :. */ COLUMN_NAME, @@ -79,7 +80,7 @@ public enum TokenType implements Serializable { /** * Represents the enumerated type for the object of type {@code BoolList} type. * This type is associated with the rule that is a collection of {@code Boolean} values - * separated by comman(,). E.g. + * separated by comma(,). E.g. * * ColumnName[,ColumnName]* * @@ -88,7 +89,7 @@ public enum TokenType implements Serializable { /** * Represents the enumerated type for the object of type {@code TextList} type. - * This type is associated with the comma separated text represented were each text + * This type is associated with the comma separated text represented where each text * is enclosed within a single quote (') or double quote (") and each text is separated * by comma (,). E.g. * @@ -104,7 +105,6 @@ public enum TokenType implements Serializable { * * Numeric[,Numeric]* * - * */ NUMERIC_LIST, @@ -120,7 +120,7 @@ public enum TokenType implements Serializable { /** * Represents the enumerated type for the object of type {@code Expression} type. - * This type is associated with code block that either represents a condition or + * This type is associated with a code block that either represents a condition or * an expression. E.g. * * exp:{ } @@ -140,7 +140,7 @@ public enum TokenType implements Serializable { /** * Represents the enumerated type for the object of type {@code Ranges} types. - * This type is associated with a collection of range represented in the form shown + * This type is associated with a collection of ranges represented in the form shown * below * * :=value[,:=value]* @@ -152,5 +152,17 @@ public enum TokenType implements Serializable { * Represents the enumerated type for the object of type {@code String} with restrictions * on characters that can be present in a string. */ - IDENTIFIER + IDENTIFIER, + + /** + * Represents the enumerated type for the object of type {@code ByteSize}. + * This type is associated with a token that represents a byte size, such as "10KB", "5MB". + */ + BYTE_SIZE, + + /** + * Represents the enumerated type for the object of type {@code TimeDuration}. + * This type is associated with a token that represents a time duration, such as "10ms", "2h". + */ + TIME_DURATION } diff --git a/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/UsageDefinition.java b/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/UsageDefinition.java index 78800b7d1..cda0bc02b 100644 --- a/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/UsageDefinition.java +++ b/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/UsageDefinition.java @@ -126,7 +126,11 @@ public String toString() { sb.append("prop:{key:value,[key:value]*"); } else if (token.type().equals(TokenType.RANGES)) { sb.append("start:end=[bool|text|numeric][,start:end=[bool|text|numeric]*"); - } + } else if (token.type().equals(TokenType.BYTE_SIZE)) { + sb.append(token.name()).append(" (e.g., 10KB, 1MB)"); + } else if (token.type().equals(TokenType.TIME_DURATION)) { + sb.append(token.name()).append(" (e.g., 100ms, 2s)"); + } } count--; @@ -234,11 +238,28 @@ public void define(String name, TokenType type, String label, boolean optional) tokens.add(spec); } + public void defineByteSize(String name) { + TokenDefinition spec = new TokenDefinition(name, TokenType.BYTE_SIZE, null, currentOrdinal, Optional.FALSE); + currentOrdinal++; + tokens.add(spec); + } + + public void defineTimeDuration(String name) { + TokenDefinition spec = new TokenDefinition(name, TokenType.TIME_DURATION, null, currentOrdinal, Optional.FALSE); + currentOrdinal++; + tokens.add(spec); + } + /** * @return a instance of UsageDefinition object. */ public UsageDefinition build() { return new UsageDefinition(directive, optionalCnt, tokens); } + + public Object addRequiredArg(String string) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'addRequiredArg'"); + } } } diff --git a/wrangler-api/src/test/java/io/cdap/wrangler/api/ByteSizeTest.java b/wrangler-api/src/test/java/io/cdap/wrangler/api/ByteSizeTest.java new file mode 100644 index 000000000..081148895 --- /dev/null +++ b/wrangler-api/src/test/java/io/cdap/wrangler/api/ByteSizeTest.java @@ -0,0 +1,62 @@ +// /* +// * Copyright © 2017-2019 Cask Data, Inc. +// * +// * 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. +// */ + +// import org.junit.Test; + +// import io.cdap.wrangler.api.parser.ByteSize; + +// import static org.junit.Assert.assertEquals; + +// public class ByteSizeTest { + +// @Test +// public void testValidByteSizeKB() { +// ByteSize byteSize = new ByteSize("10KB"); +// assertEquals(10240, byteSize.getBytes()); +// } + +// @Test +// public void testValidByteSizeMB() { +// ByteSize byteSize = new ByteSize("1MB"); +// assertEquals(1048576, byteSize.getBytes()); +// } + +// @Test +// public void testValidByteSizeGB() { +// ByteSize byteSize = new ByteSize("2GB"); +// assertEquals(2147483648L, byteSize.getBytes()); +// } + +// @Test +// public void testValidByteSizeWithoutUnit() { +// ByteSize byteSize = new ByteSize("500"); +// assertEquals(500L, byteSize.getBytes()); +// } + +// @Test +// public void testInvalidByteSize() { +// assertThrows(IllegalArgumentException.class, () -> { +// new ByteSize("invalidSize"); +// }); +// } + +// @Test +// public void testInvalidByteSizeUnit() { +// assertThrows(IllegalArgumentException.class, () -> { +// new ByteSize("10ZZ"); +// }); +// } +// } diff --git a/wrangler-core/pom.xml b/wrangler-core/pom.xml index e2dcb3c2b..8ff04f5a7 100644 --- a/wrangler-core/pom.xml +++ b/wrangler-core/pom.xml @@ -54,7 +54,7 @@ com.google.protobuf protobuf-java - ${protobuf.version} + io.cdap.cdap diff --git a/wrangler-core/src/main/antlr4/io/cdap/wrangler/parser/Directives.g4 b/wrangler-core/src/main/antlr4/io/cdap/wrangler/parser/Directives.g4 index 7c517ed6a..79aa9abd3 100644 --- a/wrangler-core/src/main/antlr4/io/cdap/wrangler/parser/Directives.g4 +++ b/wrangler-core/src/main/antlr4/io/cdap/wrangler/parser/Directives.g4 @@ -64,9 +64,12 @@ directive | stringList | numberRanges | properties + | byteSizeOne // <-- add this line + | timeDurationOne // <-- add this line )*? ; + ifStatement : ifStat elseIfStat* elseStat? '}' ; @@ -140,7 +143,7 @@ numberRange ; value - : String | Number | Column | Bool + : String | Number | Column | Bool | Byte_Size | Time_Duration ; ecommand @@ -311,3 +314,35 @@ fragment Int fragment Digit : [0-9] ; + + BYTE_SIZE + : Digit+ ('.' Digit+)? BYTE_UNIT + ; + +TIME_DURATION + : Digit+ ('.' Digit+)? TIME_UNIT + ; + +fragment BYTE_UNIT + : [kK][bB] + | [mM][bB] + | [gG][bB] + | [tT][bB] + ; + +fragment TIME_UNIT + : 'ms' + | 's' + | 'm' + | 'h' + ; + +byteSizeOne + : BYTE_SIZE + ; + +timeDurationOne + : TIME_DURATION + ; + + diff --git a/wrangler-core/src/main/java/io/cdap/directives/transformation/AggregateStats.java b/wrangler-core/src/main/java/io/cdap/directives/transformation/AggregateStats.java new file mode 100644 index 000000000..c7dbfe97f --- /dev/null +++ b/wrangler-core/src/main/java/io/cdap/directives/transformation/AggregateStats.java @@ -0,0 +1,271 @@ +/* + * Copyright © 2024 Cask Data, Inc. + * + * 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 io.cdap.directives.transformation; + +import java.util.List; + +import io.cdap.cdap.api.annotation.Description; +import io.cdap.cdap.api.annotation.Name; +import io.cdap.cdap.api.annotation.Plugin; +import io.cdap.wrangler.api.Arguments; +import io.cdap.wrangler.api.Directive; +import io.cdap.wrangler.api.DirectiveExecutionException; +import io.cdap.wrangler.api.DirectiveParseException; +import io.cdap.wrangler.api.ExecutorContext; +import io.cdap.wrangler.api.Row; +import io.cdap.wrangler.api.annotations.Categories; +import io.cdap.wrangler.api.parser.ByteSize; +import io.cdap.wrangler.api.parser.ColumnName; +import io.cdap.wrangler.api.parser.Identifier; +import io.cdap.wrangler.api.parser.TimeDuration; +import io.cdap.wrangler.api.parser.TokenType; +import io.cdap.wrangler.api.parser.UsageDefinition; + +/** + * This class implements a directive for aggregating byte size and time duration + * statistics. + * It allows users to define columns for size and time, aggregate them based on + * the specified + * aggregation type (e.g., total or average), and convert the results to the + * desired units. + */ +@Plugin(type = Directive.TYPE) +@Name("aggregate-stats") +@Categories(categories = { "transform" }) +@Description("Aggregates byte size and time duration statistics with configurable units.") +public class AggregateStats implements Directive { + private static final String NAME = "aggregate-stats"; + + private String sizeColumn; + private String timeColumn; + private String totalSizeColumn; + private String totalTimeColumn; + private String aggregationType; + private String sizeUnit; + private String timeUnit; + private boolean isLastStage = false; + private AggregationStore aggregationStore; + + @Override + public UsageDefinition define() { + UsageDefinition.Builder builder = UsageDefinition.builder(NAME); + + // Define tokens one by one + builder.define("size-column", TokenType.COLUMN_NAME); + builder.define("time-column", TokenType.COLUMN_NAME); + builder.define("total-size-column", TokenType.COLUMN_NAME); + builder.define("total-time-column", TokenType.COLUMN_NAME); + builder.define("aggregation-type", TokenType.IDENTIFIER, true); // Optional token + builder.define("size-unit", TokenType.IDENTIFIER, true); // Optional token + builder.define("time-unit", TokenType.IDENTIFIER, true); // Optional token + + // Build and return the UsageDefinition object + return builder.build(); + } + + @Override + public void initialize(Arguments args) throws DirectiveParseException { + // Initialize columns from arguments + this.sizeColumn = ((ColumnName) args.value("size-column")).value(); + this.timeColumn = ((ColumnName) args.value("time-column")).value(); + this.totalSizeColumn = ((ColumnName) args.value("total-size-column")).value(); + this.totalTimeColumn = ((ColumnName) args.value("total-time-column")).value(); + + // Initialize optional parameters with default values if not provided + this.aggregationType = args.contains("aggregation-type") ? ((Identifier) args.value("aggregation-type")).value() + : "total"; + this.sizeUnit = args.contains("size-unit") ? ((Identifier) args.value("size-unit")).value() : "bytes"; + this.timeUnit = args.contains("time-unit") ? ((Identifier) args.value("time-unit")).value() : "milliseconds"; + + // Validate parameters to ensure correct values + validateParameters(); + + // Initialize aggregation store for data + this.aggregationStore = new AggregationStore(); + } + + // Validate aggregation type, size unit, and time unit parameters + private void validateParameters() throws DirectiveParseException { + if (!aggregationType.equalsIgnoreCase("total") && !aggregationType.equalsIgnoreCase("average")) { + throw new DirectiveParseException(NAME, + "Aggregation type must be 'total' or 'average'"); + } + + if (!isValidSizeUnit(sizeUnit)) { + throw new DirectiveParseException(NAME, + "Invalid size unit. Valid units: bytes, kb, mb, gb"); + } + + if (!isValidTimeUnit(timeUnit)) { + throw new DirectiveParseException(NAME, + "Invalid time unit. Valid units: nanoseconds, milliseconds, seconds, minutes, hours"); + } + } + + // Main execution method for transforming rows + @Override + public List execute(List rows, ExecutorContext context) throws DirectiveExecutionException { + // Detect if this is the last stage of execution + if (rows == null || rows.isEmpty()) { + isLastStage = true; + } + + // Process each row for aggregation + for (Row row : rows) { + processRow(row, aggregationStore); + } + + // If it's the last stage, return the aggregated result + if (isLastStage) { + return createResultRow(aggregationStore); + } + return rows; + } + + // Process individual rows for aggregation + private void processRow(Row row, AggregationStore store) throws DirectiveExecutionException { + try { + int sizeIdx = row.find(sizeColumn); + int timeIdx = row.find(timeColumn); + + validateColumnsPresent(sizeIdx, timeIdx); + + Object sizeVal = row.getValue(sizeIdx); + Object timeVal = row.getValue(timeIdx); + + validateNonNullValues(sizeVal, timeVal); + + ByteSize byteSize = new ByteSize(sizeVal.toString()); + TimeDuration timeDuration = new TimeDuration(timeVal.toString()); + + store.add(byteSize.getBytes(), timeDuration.getMilliseconds()); + } catch (IllegalArgumentException e) { + throw new DirectiveExecutionException(NAME, + String.format("Invalid value format: %s", e.getMessage()), e); + } + } + + // Create result row after processing all input rows + private List createResultRow(AggregationStore store) { + Row result = new Row(); + long sizeValue = convertSize(store.getTotalBytes(), sizeUnit); + long timeValue = convertTime(store.getTotalMilliseconds(), timeUnit); + + // If aggregation type is average, divide by the number of items + if ("average".equalsIgnoreCase(aggregationType)) { + int count = store.getCount(); + sizeValue = count > 0 ? sizeValue / count : 0; + timeValue = count > 0 ? timeValue / count : 0; + } + + result.add(totalSizeColumn, sizeValue); + result.add(totalTimeColumn, timeValue); + + return List.of(result); + } + + // Convert size from bytes to the specified unit + private long convertSize(long bytes, String unit) { + switch (unit.toLowerCase()) { + case "kb": + return bytes / 1024; + case "mb": + return bytes / (1024 * 1024); + case "gb": + return bytes / (1024 * 1024 * 1024); + default: + return bytes; + } + } + + // Convert time from milliseconds to the specified unit + private long convertTime(long milliseconds, String unit) { + switch (unit.toLowerCase()) { + case "nanoseconds": + return milliseconds * 1_000_000; + case "seconds": + return milliseconds / 1_000; + case "minutes": + return milliseconds / (60_000); + case "hours": + return milliseconds / (3_600_000); + default: + return milliseconds; + } + } + + // Validate if the given size unit is valid + private boolean isValidSizeUnit(String unit) { + return unit.matches("(?i)bytes|kb|mb|gb"); + } + + // Validate if the given time unit is valid + private boolean isValidTimeUnit(String unit) { + return unit.matches("(?i)nanoseconds|milliseconds|seconds|minutes|hours"); + } + + // Validate if the required columns are present in the row + private void validateColumnsPresent(int sizeIdx, int timeIdx) throws DirectiveExecutionException { + if (sizeIdx == -1) { + throw new DirectiveExecutionException(NAME, + String.format("Size column '%s' not found", sizeColumn)); + } + if (timeIdx == -1) { + throw new DirectiveExecutionException(NAME, + String.format("Time column '%s' not found", timeColumn)); + } + } + + // Validate that size and time values are not null + private void validateNonNullValues(Object sizeVal, Object timeVal) throws DirectiveExecutionException { + if (sizeVal == null || timeVal == null) { + throw new DirectiveExecutionException(NAME, + "Null values not allowed in size/time columns"); + } + } + + @Override + public void destroy() { + // No resources to clean up + } + + // Internal class to store aggregation results + private static class AggregationStore { + private long totalBytes; + private long totalMilliseconds; + private int count; + + void add(long bytes, long milliseconds) { + totalBytes += bytes; + totalMilliseconds += milliseconds; + count++; + } + + long getTotalBytes() { + return totalBytes; + } + + long getTotalMilliseconds() { + return totalMilliseconds; + } + + int getCount() { + return count; + } + } +} + diff --git a/wrangler-core/src/main/java/io/cdap/functions/JsonFunctions.java b/wrangler-core/src/main/java/io/cdap/functions/JsonFunctions.java index d07f24cfb..560a87d04 100644 --- a/wrangler-core/src/main/java/io/cdap/functions/JsonFunctions.java +++ b/wrangler-core/src/main/java/io/cdap/functions/JsonFunctions.java @@ -324,7 +324,7 @@ public static String Stringify(JsonElement element) { /** * @return Number of elements in the array. */ - @Nullable + // @Nullable public static int ArrayLength(JsonArray array) { if (array != null) { return array.size(); diff --git a/wrangler-core/src/main/java/io/cdap/wrangler/dq/ConvertDistances.java b/wrangler-core/src/main/java/io/cdap/wrangler/dq/ConvertDistances.java index 1be87b116..864599b55 100644 --- a/wrangler-core/src/main/java/io/cdap/wrangler/dq/ConvertDistances.java +++ b/wrangler-core/src/main/java/io/cdap/wrangler/dq/ConvertDistances.java @@ -103,7 +103,7 @@ public ConvertDistances() { this(Distance.MILE, Distance.KILOMETER); } - @Nullable + // @Nullable public ConvertDistances(Distance from, Distance to) { this.from = (from == null ? Distance.MILE : from); this.to = (to == null ? Distance.KILOMETER : to); diff --git a/wrangler-core/src/main/java/io/cdap/wrangler/expression/ELContext.java b/wrangler-core/src/main/java/io/cdap/wrangler/expression/ELContext.java index 04b0b884b..1e8b83b7a 100644 --- a/wrangler-core/src/main/java/io/cdap/wrangler/expression/ELContext.java +++ b/wrangler-core/src/main/java/io/cdap/wrangler/expression/ELContext.java @@ -91,7 +91,7 @@ public ELContext(ExecutorContext context, EL el, Row row) { set("this", row); } - @Nullable + // @Nullable private void init(ExecutorContext context) { if (context != null) { // Adds the transient store variables. diff --git a/wrangler-core/src/main/java/io/cdap/wrangler/parser/RecipeCompiler.java b/wrangler-core/src/main/java/io/cdap/wrangler/parser/RecipeCompiler.java index fa3811ea3..add11b47b 100644 --- a/wrangler-core/src/main/java/io/cdap/wrangler/parser/RecipeCompiler.java +++ b/wrangler-core/src/main/java/io/cdap/wrangler/parser/RecipeCompiler.java @@ -13,13 +13,11 @@ * License for the specific language governing permissions and limitations under * the License. */ - package io.cdap.wrangler.parser; -import io.cdap.wrangler.api.CompileException; -import io.cdap.wrangler.api.CompileStatus; -import io.cdap.wrangler.api.Compiler; -import io.cdap.wrangler.api.RecipeSymbol; +import java.io.InputStream; +import java.nio.file.Path; + import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; @@ -27,61 +25,63 @@ import org.antlr.v4.tool.GrammarParserInterpreter; import org.apache.twill.filesystem.Location; -import java.io.InputStream; -import java.nio.file.Path; +import io.cdap.wrangler.api.CompileException; +import io.cdap.wrangler.api.CompileStatus; +import io.cdap.wrangler.api.Compiler; +import io.cdap.wrangler.api.RecipeSymbol; // Moved this import here for order /** * Class description here. */ public final class RecipeCompiler implements Compiler { - @Override - public CompileStatus compile(String recipe) throws CompileException { - return compile(CharStreams.fromString(recipe)); - } + @Override + public CompileStatus compile(String recipe) throws CompileException { + return compile(CharStreams.fromString(recipe)); + } - @Override - public CompileStatus compile(Location location) throws CompileException { - try (InputStream is = location.getInputStream()) { - return compile(CharStreams.fromStream(is)); - } catch (Exception e) { - throw new CompileException(e.getMessage(), e); + @Override + public CompileStatus compile(Location location) throws CompileException { + try (InputStream is = location.getInputStream()) { + return compile(CharStreams.fromStream(is)); + } catch (Exception e) { + throw new CompileException(e.getMessage(), e); + } } - } - @Override - public CompileStatus compile(Path path) throws CompileException { - try { - return compile(CharStreams.fromPath(path)); - } catch (Exception e) { - throw new CompileException(e.getMessage(), e); + @Override + public CompileStatus compile(Path path) throws CompileException { + try { + return compile(CharStreams.fromPath(path)); + } catch (Exception e) { + throw new CompileException(e.getMessage(), e); + } } - } - private CompileStatus compile(CharStream stream) throws CompileException { - try { - SyntaxErrorListener errorListener = new SyntaxErrorListener(); - DirectivesLexer lexer = new DirectivesLexer(stream); - lexer.removeErrorListeners(); - lexer.addErrorListener(errorListener); + private CompileStatus compile(CharStream stream) throws CompileException { + try { + SyntaxErrorListener errorListener = new SyntaxErrorListener(); + DirectivesLexer lexer = new DirectivesLexer(stream); + lexer.removeErrorListeners(); + lexer.addErrorListener(errorListener); - DirectivesParser parser = new DirectivesParser(new CommonTokenStream(lexer)); - parser.removeErrorListeners(); - parser.addErrorListener(errorListener); - parser.setErrorHandler(new GrammarParserInterpreter.BailButConsumeErrorStrategy()); - parser.setBuildParseTree(true); - ParseTree tree = parser.statements(); + DirectivesParser parser = new DirectivesParser(new CommonTokenStream(lexer)); + parser.removeErrorListeners(); + parser.addErrorListener(errorListener); + parser.setErrorHandler(new GrammarParserInterpreter.BailButConsumeErrorStrategy()); + parser.setBuildParseTree(true); + ParseTree tree = parser.statements(); - if (errorListener.hasErrors()) { - return new CompileStatus(true, errorListener.iterator()); - } + if (errorListener.hasErrors()) { + return new CompileStatus(true, errorListener.iterator()); + } - RecipeVisitor visitor = new RecipeVisitor(); - visitor.visit(tree); - RecipeSymbol symbol = visitor.getCompiledUnit(); - return new CompileStatus(symbol); - } catch (StringIndexOutOfBoundsException e) { - throw new CompileException("Issue in compiling directives"); + RecipeVisitor visitor = new RecipeVisitor(); + visitor.visit(tree); + RecipeSymbol symbol = visitor.getCompiledUnit(); + return new CompileStatus(symbol); + } catch (StringIndexOutOfBoundsException e) { + throw new CompileException("Issue in compiling directives"); + } } - } } diff --git a/wrangler-core/src/main/java/io/cdap/wrangler/parser/RecipeVisitor.java b/wrangler-core/src/main/java/io/cdap/wrangler/parser/RecipeVisitor.java index ac35e7a5e..ca8387774 100644 --- a/wrangler-core/src/main/java/io/cdap/wrangler/parser/RecipeVisitor.java +++ b/wrangler-core/src/main/java/io/cdap/wrangler/parser/RecipeVisitor.java @@ -1,27 +1,37 @@ /* * Copyright © 2017-2019 Cask Data, Inc. * - * 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 + * 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. + * 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 io.cdap.wrangler.parser; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.misc.Interval; +import org.antlr.v4.runtime.tree.TerminalNode; + import io.cdap.wrangler.api.LazyNumber; import io.cdap.wrangler.api.RecipeSymbol; import io.cdap.wrangler.api.SourceInfo; import io.cdap.wrangler.api.Triplet; import io.cdap.wrangler.api.parser.Bool; import io.cdap.wrangler.api.parser.BoolList; +import io.cdap.wrangler.api.parser.ByteSize; import io.cdap.wrangler.api.parser.ColumnName; import io.cdap.wrangler.api.parser.ColumnNameList; import io.cdap.wrangler.api.parser.DirectiveName; @@ -33,173 +43,95 @@ import io.cdap.wrangler.api.parser.Ranges; import io.cdap.wrangler.api.parser.Text; import io.cdap.wrangler.api.parser.TextList; +import io.cdap.wrangler.api.parser.TimeDuration; import io.cdap.wrangler.api.parser.Token; -import org.antlr.v4.runtime.ParserRuleContext; -import org.antlr.v4.runtime.misc.Interval; -import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.TerminalNode; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; /** - * This class RecipeVisitor implements the visitor pattern - * used during traversal of the AST tree. The ParserTree#Walker - * invokes appropriate methods as call backs with information about the node. - * - *

In order to understand what's being invoked, please look at the grammar file - * Directive.g4

. - * - *

This class exposes a getTokenGroups method for retrieving the - * RecipeSymbol after visiting. The RecipeSymbol represents - * all the TokenGroup for all directives in a recipe. Each directive - * will create a TokenGroup

- * - *

As the ParseTree is walking through the call graph, it generates - * one TokenGroup for each directive in the recipe. Each TokenGroup - * contains parsed Tokens for that directive along with more information like - * SourceInfo. A collection of TokenGroup consistutes a RecipeSymbol - * that is returned by this function.

+ * Visitor class to parse directives and produce RecipeSymbol. */ public final class RecipeVisitor extends DirectivesBaseVisitor { - private RecipeSymbol.Builder builder = new RecipeSymbol.Builder(); + private final RecipeSymbol.Builder builder = new RecipeSymbol.Builder(); - /** - * Returns a RecipeSymbol for the recipe being parsed. This - * object has all the tokens that were successfully parsed along with source - * information for each directive in the recipe. - * - * @return An compiled object after parsing the recipe. - */ public RecipeSymbol getCompiledUnit() { return builder.build(); } - /** - * A Recipe is made up of Directives and Directives is made up of each individual - * Directive. This method is invoked on every visit to a new directive in the recipe. - */ @Override public RecipeSymbol.Builder visitDirective(DirectivesParser.DirectiveContext ctx) { builder.createTokenGroup(getOriginalSource(ctx)); return super.visitDirective(ctx); } - /** - * A Directive can include identifiers, this method extracts that token that is being - * identified as token of type Identifier. - */ @Override public RecipeSymbol.Builder visitIdentifier(DirectivesParser.IdentifierContext ctx) { builder.addToken(new Identifier(ctx.Identifier().getText())); return super.visitIdentifier(ctx); } - /** - * A Directive can include properties (which are a collection of key and value pairs), - * this method extracts that token that is being identified as token of type Properties. - */ @Override public RecipeSymbol.Builder visitPropertyList(DirectivesParser.PropertyListContext ctx) { Map props = new HashMap<>(); - List properties = ctx.property(); - for (DirectivesParser.PropertyContext property : properties) { - String identifier = property.Identifier().getText(); - Token token; + for (DirectivesParser.PropertyContext property : ctx.property()) { + String key = property.Identifier().getText(); + Token valueToken; if (property.number() != null) { - token = new Numeric(new LazyNumber(property.number().getText())); + valueToken = new Numeric(new LazyNumber(property.number().getText())); } else if (property.bool() != null) { - token = new Bool(Boolean.valueOf(property.bool().getText())); + valueToken = new Bool(Boolean.parseBoolean(property.bool().getText())); } else { - String text = property.text().getText(); - token = new Text(text.substring(1, text.length() - 1)); + String rawText = property.text().getText(); + valueToken = new Text(rawText.substring(1, rawText.length() - 1)); } - props.put(identifier, token); + props.put(key, valueToken); } builder.addToken(new Properties(props)); return builder; } - /** - * A Pragma is an instruction to the compiler to dynamically load the directives being specified - * from the DirectiveRegistry. These do not affect the data flow. - * - *

E.g. #pragma load-directives test1, test2, test3; will collect the tokens - * test1, test2 and test3 as dynamically loadable directives.

- */ @Override public RecipeSymbol.Builder visitPragmaLoadDirective(DirectivesParser.PragmaLoadDirectiveContext ctx) { - List identifiers = ctx.identifierList().Identifier(); - for (TerminalNode identifier : identifiers) { + for (TerminalNode identifier : ctx.identifierList().Identifier()) { builder.addLoadableDirective(identifier.getText()); } return builder; } - /** - * A Pragma version is a informational directive to notify compiler about the grammar that is should - * be using to parse the directives below. - */ @Override public RecipeSymbol.Builder visitPragmaVersion(DirectivesParser.PragmaVersionContext ctx) { builder.addVersion(ctx.Number().getText()); return builder; } - /** - * A Directive can include number ranges like start:end=value[,start:end=value]*. This - * visitor method allows you to collect all the number ranges and create a token type - * Ranges. - */ @Override public RecipeSymbol.Builder visitNumberRanges(DirectivesParser.NumberRangesContext ctx) { - List> output = new ArrayList<>(); - List ranges = ctx.numberRange(); - for (DirectivesParser.NumberRangeContext range : ranges) { - List numbers = range.Number(); + List> ranges = new ArrayList<>(); + for (DirectivesParser.NumberRangeContext range : ctx.numberRange()) { String text = range.value().getText(); if (text.startsWith("'") && text.endsWith("'")) { text = text.substring(1, text.length() - 1); } - Triplet val = - new Triplet<>(new Numeric(new LazyNumber(numbers.get(0).getText())), - new Numeric(new LazyNumber(numbers.get(1).getText())), - text - ); - output.add(val); + ranges.add(new Triplet<>( + new Numeric(new LazyNumber(range.Number(0).getText())), + new Numeric(new LazyNumber(range.Number(1).getText())), + text)); } - builder.addToken(new Ranges(output)); + builder.addToken(new Ranges(ranges)); return builder; } - /** - * This visitor method extracts the custom directive name specified. The custom - * directives are specified with a bang (!) at the start. - */ @Override public RecipeSymbol.Builder visitEcommand(DirectivesParser.EcommandContext ctx) { builder.addToken(new DirectiveName(ctx.Identifier().getText())); return builder; } - /** - * A Directive can consist of column specifiers. These are columns that the directive - * would operate on. When a token of type column is visited, it would generate a token - * type of type ColumnName. - */ @Override public RecipeSymbol.Builder visitColumn(DirectivesParser.ColumnContext ctx) { builder.addToken(new ColumnName(ctx.Column().getText().substring(1))); return builder; } - /** - * A Directive can consist of text field. These type of fields are enclosed within - * a single-quote or a double-quote. This visitor method extracts the string value - * within the quotes and creates a token type Text. - */ @Override public RecipeSymbol.Builder visitText(DirectivesParser.TextContext ctx) { String value = ctx.String().getText(); @@ -207,123 +139,92 @@ public RecipeSymbol.Builder visitText(DirectivesParser.TextContext ctx) { return builder; } - /** - * A Directive can consist of numeric field. This visitor method extracts the - * numeric value Numeric. - */ @Override public RecipeSymbol.Builder visitNumber(DirectivesParser.NumberContext ctx) { - LazyNumber number = new LazyNumber(ctx.Number().getText()); - builder.addToken(new Numeric(number)); + builder.addToken(new Numeric(new LazyNumber(ctx.Number().getText()))); return builder; } - /** - * A Directive can consist of Bool field. The Bool field is represented as - * either true or false. This visitor method extract the bool value into a - * token type Bool. - */ @Override public RecipeSymbol.Builder visitBool(DirectivesParser.BoolContext ctx) { - builder.addToken(new Bool(Boolean.valueOf(ctx.Bool().getText()))); + builder.addToken(new Bool(Boolean.parseBoolean(ctx.Bool().getText()))); return builder; } - /** - * A Directive can include a expression or a condition to be evaluated. When - * such a token type is found, the visitor extracts the expression and generates - * a token type Expression to be added to the TokenGroup - */ @Override public RecipeSymbol.Builder visitCondition(DirectivesParser.ConditionContext ctx) { - int childCount = ctx.getChildCount(); - StringBuilder sb = new StringBuilder(); - for (int i = 1; i < childCount - 1; ++i) { - ParseTree child = ctx.getChild(i); - sb.append(child.getText()).append(" "); + StringBuilder expr = new StringBuilder(); + for (int i = 1; i < ctx.getChildCount() - 1; ++i) { + expr.append(ctx.getChild(i).getText()).append(" "); } - builder.addToken(new Expression(sb.toString())); + builder.addToken(new Expression(expr.toString().trim())); return builder; } - /** - * A Directive has name and in the parsing context it's called a command. - * This visitor methods extracts the command and creates a toke type DirectiveName - */ @Override public RecipeSymbol.Builder visitCommand(DirectivesParser.CommandContext ctx) { builder.addToken(new DirectiveName(ctx.Identifier().getText())); return builder; } - /** - * This visitor methods extracts the list of columns specified. It creates a token - * type ColumnNameList to be added to TokenGroup. - */ @Override public RecipeSymbol.Builder visitColList(DirectivesParser.ColListContext ctx) { - List columns = ctx.Column(); - List names = new ArrayList<>(); - for (TerminalNode column : columns) { - names.add(column.getText().substring(1)); + List columns = new ArrayList<>(); + for (TerminalNode column : ctx.Column()) { + columns.add(column.getText().substring(1)); } - builder.addToken(new ColumnNameList(names)); + builder.addToken(new ColumnNameList(columns)); return builder; } - /** - * This visitor methods extracts the list of numeric specified. It creates a token - * type NumericList to be added to TokenGroup. - */ @Override public RecipeSymbol.Builder visitNumberList(DirectivesParser.NumberListContext ctx) { - List numbers = ctx.Number(); - List numerics = new ArrayList<>(); - for (TerminalNode number : numbers) { - numerics.add(new LazyNumber(number.getText())); + List numbers = new ArrayList<>(); + for (TerminalNode number : ctx.Number()) { + numbers.add(new LazyNumber(number.getText())); } - builder.addToken(new NumericList(numerics)); + builder.addToken(new NumericList(numbers)); return builder; } - /** - * This visitor methods extracts the list of booleans specified. It creates a token - * type BoolList to be added to TokenGroup. - */ @Override public RecipeSymbol.Builder visitBoolList(DirectivesParser.BoolListContext ctx) { - List bools = ctx.Bool(); - List booleans = new ArrayList<>(); - for (TerminalNode bool : bools) { - booleans.add(Boolean.parseBoolean(bool.getText())); + List bools = new ArrayList<>(); + for (TerminalNode bool : ctx.Bool()) { + bools.add(Boolean.parseBoolean(bool.getText())); } - builder.addToken(new BoolList(booleans)); + builder.addToken(new BoolList(bools)); return builder; } - /** - * This visitor methods extracts the list of strings specified. It creates a token - * type StringList to be added to TokenGroup. - */ @Override public RecipeSymbol.Builder visitStringList(DirectivesParser.StringListContext ctx) { - List strings = ctx.String(); - List strs = new ArrayList<>(); - for (TerminalNode string : strings) { + List strings = new ArrayList<>(); + for (TerminalNode string : ctx.String()) { String text = string.getText(); - strs.add(text.substring(1, text.length() - 1)); + strings.add(text.substring(1, text.length() - 1)); } - builder.addToken(new TextList(strs)); + builder.addToken(new TextList(strings)); + return builder; + } + + @Override + public RecipeSymbol.Builder visitByteSizeOne(DirectivesParser.ByteSizeOneContext ctx) { + builder.addToken(new ByteSize(ctx.getText())); + return builder; + } + + @Override + public RecipeSymbol.Builder visitTimeDurationOne(DirectivesParser.TimeDurationOneContext ctx) { + builder.addToken(new TimeDuration(ctx.getText())); return builder; } private SourceInfo getOriginalSource(ParserRuleContext ctx) { - int a = ctx.getStart().getStartIndex(); - int b = ctx.getStop().getStopIndex(); - Interval interval = new Interval(a, b); - String text = ctx.start.getInputStream().getText(interval); - int lineno = ctx.getStart().getLine(); - int column = ctx.getStart().getCharPositionInLine(); - return new SourceInfo(lineno, column, text); + Interval interval = new Interval(ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex()); + String sourceText = ctx.getStart().getInputStream().getText(interval); + return new SourceInfo(ctx.getStart().getLine(), ctx.getStart().getCharPositionInLine(), sourceText); } } + + diff --git a/wrangler-core/src/test/java/com/example/tutorial/AddressBookProtos.java b/wrangler-core/src/test/java/com/example/tutorial/AddressBookProtos.java index 0ae24c7de..d94dae3da 100644 --- a/wrangler-core/src/test/java/com/example/tutorial/AddressBookProtos.java +++ b/wrangler-core/src/test/java/com/example/tutorial/AddressBookProtos.java @@ -616,6 +616,7 @@ public Builder clear() { return this; } + @Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { return com.example.tutorial.AddressBookProtos.internal_static_tutorial_Person_PhoneNumber_descriptor; @@ -641,27 +642,33 @@ public com.example.tutorial.AddressBookProtos.Person.PhoneNumber buildPartial() return result; } + @Override public Builder clone() { return (Builder) super.clone(); } + @Override public Builder setField( com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { return (Builder) super.setField(field, value); } + @Override public Builder clearField( com.google.protobuf.Descriptors.FieldDescriptor field) { return (Builder) super.clearField(field); } + @Override public Builder clearOneof( com.google.protobuf.Descriptors.OneofDescriptor oneof) { return (Builder) super.clearOneof(oneof); } + @Override public Builder setRepeatedField( com.google.protobuf.Descriptors.FieldDescriptor field, int index, Object value) { return (Builder) super.setRepeatedField(field, index, value); } + @Override public Builder addRepeatedField( com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { @@ -689,10 +696,12 @@ public Builder mergeFrom(com.example.tutorial.AddressBookProtos.Person.PhoneNumb return this; } + @Override public final boolean isInitialized() { return true; } + @Override public Builder mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) @@ -1254,6 +1263,7 @@ public com.example.tutorial.AddressBookProtos.Person buildPartial() { return result; } + @Override public Builder clone() { return (Builder) super.clone(); } @@ -2184,6 +2194,7 @@ public com.example.tutorial.AddressBookProtos.AddressBook buildPartial() { return result; } + @Override public Builder clone() { return (Builder) super.clone(); }