+ * 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; /** - * TheTokenDefinition 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 thisTokenDefinition. 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 :
* 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();
}