提交 6c4afe1f 编写于 作者: F Fabian Hueske

Completed, updated, and improved JavaDocs of Java API

上级 d3df8dca
......@@ -31,6 +31,7 @@ import eu.stratosphere.api.java.io.TextOutputFormat;
import eu.stratosphere.api.java.operators.AggregateOperator;
import eu.stratosphere.api.java.operators.CoGroupOperator;
import eu.stratosphere.api.java.operators.CoGroupOperator.CoGroupOperatorSets;
import eu.stratosphere.api.java.operators.CrossOperator.DefaultCross;
import eu.stratosphere.api.java.operators.CrossOperator;
import eu.stratosphere.api.java.operators.CustomUnaryOperation;
import eu.stratosphere.api.java.operators.DataSink;
......@@ -46,6 +47,7 @@ import eu.stratosphere.api.java.operators.ReduceGroupOperator;
import eu.stratosphere.api.java.operators.ReduceOperator;
import eu.stratosphere.api.java.operators.UnionOperator;
import eu.stratosphere.api.java.tuple.Tuple;
import eu.stratosphere.api.java.tuple.Tuple2;
import eu.stratosphere.api.java.typeutils.InputTypeConfigurable;
import eu.stratosphere.api.java.typeutils.TypeInformation;
import eu.stratosphere.core.fs.FileSystem.WriteMode;
......@@ -418,23 +420,41 @@ public abstract class DataSet<T> {
// Cross
// --------------------------------------------------------------------------------------------
/**
* Continues a Join transformation and defines the {@link Tuple} fields of the second join
* {@link DataSet} that should be used as join keys.<br/>
* <b>Note: Fields can only be selected as join keys on Tuple DataSets.</b><br/>
*
* The resulting {@link DefaultJoin} wraps each pair of joining elements into a {@link Tuple2}, with
* the element of the first input being the first field of the tuple and the element of the
* second input being the second field of the tuple.
*
* @param fields The indexes of the Tuple fields of the second join DataSet that should be used as keys.
* @return A DefaultJoin that represents the joined DataSet.
*/
/**
* Initiates a Cross transformation.<br/>
* A Cross transformation combines the elements of two
* {@link DataSet DataSets} into one DataSet. It builds all pair combinations of elements of
* both DataSets, i.e., it builds a Cartesian product, and calls a {@link CrossFunction} for
* each pair of elements.</br>
* The CrossFunction returns a exactly one element for each pair of input elements.</br>
* This method returns a {@link CrossOperatorSets} on which
* {@link CrossOperatorSets#with()} needs to be called to define the CrossFunction that
* is applied.
* both DataSets, i.e., it builds a Cartesian product.
*
* <p>
* The resulting {@link DefaultCross} wraps each pair of crossed elements into a {@link Tuple2}, with
* the element of the first input being the first field of the tuple and the element of the
* second input being the second field of the tuple.
*
* <p>
* Call {@link DefaultCross.with(CrossFunction)} to define a {@link CrossFunction} which is called for
* each pair of crossed elements. The CrossFunction returns a exactly one element for each pair of input elements.</br>
*
* @param other The other DataSet with which this DataSet is crossed.
* @return A CrossOperatorSets to continue the definition of the Cross transformation.
* @return A DefaultCross that returns a Tuple2 for each pair of crossed elements.
*
* @see DefaultCross
* @see CrossFunction
* @see CrossOperatorSets
* @see DataSet
* @see Tuple2
*/
public <R> CrossOperator.DefaultCross<T, R> cross(DataSet<R> other) {
return new CrossOperator.DefaultCross<T, R>(this, other);
......@@ -444,21 +464,26 @@ public abstract class DataSet<T> {
* Initiates a Cross transformation.<br/>
* A Cross transformation combines the elements of two
* {@link DataSet DataSets} into one DataSet. It builds all pair combinations of elements of
* both DataSets, i.e., it builds a Cartesian product, and calls a {@link CrossFunction} for
* each pair of elements.</br>
* The CrossFunction returns a exactly one element for each pair of input elements.</br>
* both DataSets, i.e., it builds a Cartesian product.
* This method also gives the hint to the optimizer that the second DataSet to cross is much
* smaller than the first one.</br>
* This method returns a {@link CrossOperatorSets CrossOperatorSet} on which
* {@link CrossOperatorSets#with()} needs to be called to define the CrossFunction that
* is applied.
* smaller than the first one.
*
* <p>
* The resulting {@link DefaultCross} wraps each pair of crossed elements into a {@link Tuple2}, with
* the element of the first input being the first field of the tuple and the element of the
* second input being the second field of the tuple.
*
* <p>
* Call {@link DefaultCross.with(CrossFunction)} to define a {@link CrossFunction} which is called for
* each pair of crossed elements. The CrossFunction returns a exactly one element for each pair of input elements.</br>
*
* @param other The other DataSet with which this DataSet is crossed.
* @return A CrossOperatorSets to continue the definition of the Cross transformation.
* @return A DefaultCross that returns a Tuple2 for each pair of crossed elements.
*
* @see DefaultCross
* @see CrossFunction
* @see CrossOperatorSets
* @see DataSet
* @see Tuple2
*/
public <R> CrossOperator.DefaultCross<T, R> crossWithTiny(DataSet<R> other) {
return new CrossOperator.DefaultCross<T, R>(this, other);
......@@ -468,21 +493,26 @@ public abstract class DataSet<T> {
* Initiates a Cross transformation.<br/>
* A Cross transformation combines the elements of two
* {@link DataSet DataSets} into one DataSet. It builds all pair combinations of elements of
* both DataSets, i.e., it builds a Cartesian product, and calls a {@link CrossFunction} for
* each pair of elements.</br>
* The CrossFunction returns a exactly one element for each pair of input elements.</br>
* both DataSets, i.e., it builds a Cartesian product.
* This method also gives the hint to the optimizer that the second DataSet to cross is much
* larger than the first one.</br>
* This method returns a {@link CrossOperatorSets CrossOperatorSet} on which
* {@link CrossOperatorSets#with()} needs to be called to define the CrossFunction that
* is applied.
* larger than the first one.
*
* <p>
* The resulting {@link DefaultCross} wraps each pair of crossed elements into a {@link Tuple2}, with
* the element of the first input being the first field of the tuple and the element of the
* second input being the second field of the tuple.
*
* <p>
* Call {@link DefaultCross.with(CrossFunction)} to define a {@link CrossFunction} which is called for
* each pair of crossed elements. The CrossFunction returns a exactly one element for each pair of input elements.</br>
*
* @param other The other DataSet with which this DataSet is crossed.
* @return A CrossOperatorSets to continue the definition of the Cross transformation.
* @return A DefaultCross that returns a Tuple2 for each pair of crossed elements.
*
* @see DefaultCross
* @see CrossFunction
* @see CrossOperatorSets
* @see DataSet
* @see Tuple2
*/
public <R> CrossOperator.DefaultCross<T, R> crossWithHuge(DataSet<R> other) {
return new CrossOperator.DefaultCross<T, R>(this, other);
......@@ -766,7 +796,7 @@ public abstract class DataSet<T> {
}
/**
* Processes a DataSet using an {@link OutputFormat}. This method adds a data sink to the program.
* Emits a DataSet using an {@link OutputFormat}. This method adds a data sink to the program.
* Programs may have multiple data sinks. A DataSet may also have multiple consumers (data sinks
* or transformations) at the same time.
*
......
......@@ -26,13 +26,20 @@ import eu.stratosphere.api.java.operators.translation.PlanCogroupOperator;
import eu.stratosphere.api.java.operators.translation.PlanMapOperator;
import eu.stratosphere.api.java.operators.translation.PlanUnwrappingCoGroupOperator;
import eu.stratosphere.api.java.operators.translation.TupleKeyExtractingMapper;
import eu.stratosphere.api.java.tuple.Tuple;
import eu.stratosphere.api.java.tuple.Tuple2;
import eu.stratosphere.api.java.typeutils.TupleTypeInfo;
import eu.stratosphere.api.java.typeutils.TypeExtractor;
import eu.stratosphere.api.java.typeutils.TypeInformation;
/**
*
* A {@link DataSet} that is the result of a CoGroup transformation.
*
* @param <I1> The type of the first input DataSet of the CoGroup transformation.
* @param <I2> The type of the second input DataSet of the CoGroup transformation.
* @param <OUT> The type of the result of the CoGroup transformation.
*
* @see DataSet
*/
public class CoGroupOperator<I1, I2, OUT> extends TwoInputUdfOperator<I1, I2, OUT, CoGroupOperator<I1, I2, OUT>> {
......@@ -266,6 +273,14 @@ public class CoGroupOperator<I1, I2, OUT> extends TwoInputUdfOperator<I1, I2, OU
// Builder classes for incremental construction
// --------------------------------------------------------------------------------------------
/**
* Intermediate step of a CoGroup transformation. <br/>
* To continue the CoGroup transformation, select the grouping key of the first input {@link DataSet} by calling
* {@link CoGroupOperatorSets#where(int...)} or {@link CoGroupOperatorSets#where(KeySelector)}.
*
* @param <I1> The type of the first input DataSet of the CoGroup transformation.
* @param <I2> The type of the second input DataSet of the CoGroup transformation.
*/
public static final class CoGroupOperatorSets<I1, I2> {
private final DataSet<I1> input1;
......@@ -280,20 +295,46 @@ public class CoGroupOperator<I1, I2, OUT> extends TwoInputUdfOperator<I1, I2, OU
this.input2 = input2;
}
/**
* Continues a CoGroup transformation. <br/>
* Defines the {@link Tuple} fields of the first co-grouped {@link DataSet} that should be used as grouping keys.<br/>
* <b>Note: Fields can only be selected as grouping keys on Tuple DataSets.</b><br/>
*
* @param fields The indexes of the Tuple fields of the first co-grouped DataSets that should be used as keys.
* @return An incomplete CoGroup transformation.
* Call {@link CoGroupOperatorSetsPredicate#equalTo()} to continue the CoGroup.
*
* @see Tuple
* @see DataSet
*/
public CoGroupOperatorSetsPredicate where(int... fields) {
return new CoGroupOperatorSetsPredicate(new Keys.FieldPositionKeys<I1>(fields, input1.getType()));
}
/**
* Continues a CoGroup transformation and defines a {@link KeySelector} function for the first co-grouped {@link DataSet}.</br>
* The KeySelector function is called for each element of the first DataSet and extracts a single
* key value on which the DataSet is grouped. </br>
*
* @param keySelector The KeySelector function which extracts the key values from the DataSet on which it is grouped.
* @return An incomplete CoGroup transformation.
* Call {@link CoGroupOperatorSetsPredicate#equalTo()} to continue the CoGroup.
*
* @see KeySelector
* @see DataSet
*/
public <K> CoGroupOperatorSetsPredicate where(KeySelector<I1, K> keyExtractor) {
return new CoGroupOperatorSetsPredicate(new Keys.SelectorFunctionKeys<I1, K>(keyExtractor, input1.getType()));
}
public CoGroupOperatorSetsPredicate where(String keyExpression) {
return new CoGroupOperatorSetsPredicate(new Keys.ExpressionKeys<I1>(keyExpression, input1.getType()));
}
// ----------------------------------------------------------------------------------------
/**
* Intermediate step of a CoGroup transformation. <br/>
* To continue the CoGroup transformation, select the grouping key of the second input {@link DataSet} by calling
* {@link CoGroupOperatorSetsPredicate#equalTo(int...)} or {@link CoGroupOperatorSetsPredicate#equalTo(KeySelector)}.
*
*/
public final class CoGroupOperatorSetsPredicate {
private final Keys<I1> keys1;
......@@ -310,21 +351,39 @@ public class CoGroupOperator<I1, I2, OUT> extends TwoInputUdfOperator<I1, I2, OU
this.keys1 = keys1;
}
/**
* Continues a CoGroup transformation and defines the {@link Tuple} fields of the second co-grouped
* {@link DataSet} that should be used as grouping keys.<br/>
* <b>Note: Fields can only be selected as grouping keys on Tuple DataSets.</b><br/>
*
* @param fields The indexes of the Tuple fields of the second co-grouped DataSet that should be used as keys.
* @return An incomplete CoGroup transformation.
* Call {@link CoGroupOperatorWithoutFunction#with(CoGroupFunction))} to finalize the CoGroup transformation.
*/
public CoGroupOperatorWithoutFunction equalTo(int... fields) {
return createCoGroupOperator(new Keys.FieldPositionKeys<I2>(fields, input2.getType()));
}
/**
* Continues a CoGroup transformation and defines a {@link KeySelector} function for the second co-grouped {@link DataSet}.</br>
* The KeySelector function is called for each element of the second DataSet and extracts a single
* key value on which the DataSet is grouped. </br>
*
* @param keySelector The KeySelector function which extracts the key values from the second DataSet on which it is grouped.
* @return An incomplete CoGroup transformation.
* Call {@link CoGroupOperatorWithoutFunction#with(CoGroupFunction))} to finalize the CoGroup transformation.
*/
public <K> CoGroupOperatorWithoutFunction equalTo(KeySelector<I2, K> keyExtractor) {
return createCoGroupOperator(new Keys.SelectorFunctionKeys<I2, K>(keyExtractor, input2.getType()));
}
public CoGroupOperatorWithoutFunction equalTo(String keyExpression) {
return createCoGroupOperator(new Keys.ExpressionKeys<I2>(keyExpression, input2.getType()));
}
/**
* Intermediate step of a CoGroup transformation. <br/>
* To continue the CoGroup transformation, provide a {@link CoGroupFunction} by calling
* {@link CoGroupOperatorWithoutFunction#with(CoGroupFunction))}.
*
*/
private CoGroupOperatorWithoutFunction createCoGroupOperator(Keys<I2> keys2) {
if (keys2 == null) {
throw new NullPointerException();
......@@ -356,6 +415,16 @@ public class CoGroupOperator<I1, I2, OUT> extends TwoInputUdfOperator<I1, I2, OU
this.keys2 = keys2;
}
/**
* Finalizes a CoGroup transformation by applying a {@link CoGroupFunction} to groups of elements with identical keys.<br/>
* Each CoGroupFunction call returns an arbitrary number of keys.
*
* @param function The CoGroupFunction that is called for all groups of elements with identical keys.
* @return An CoGroupOperator that represents the co-grouped result DataSet.
*
* @see CoGroupFunction
* @see DataSet
*/
public <R> CoGroupOperator<I1, I2, R> with(CoGroupFunction<I1, I2, R> function) {
TypeInformation<R> returnType = TypeExtractor.getCoGroupReturnTypes(function, input1.getType(), input2.getType());
return new CoGroupOperator<I1, I2, R>(input1, input2, keys1, keys2, function, returnType);
......
......@@ -20,12 +20,12 @@ import eu.stratosphere.api.common.operators.Operator;
import eu.stratosphere.api.java.DataSet;
import eu.stratosphere.api.java.functions.CrossFunction;
import eu.stratosphere.api.java.operators.translation.PlanCrossOperator;
//CHECKSTYLE.OFF: AvoidStarImport - Needed for TupleGenerator
import eu.stratosphere.api.java.tuple.*;
//CHECKSTYLE.ON: AvoidStarImport
import eu.stratosphere.api.java.typeutils.TupleTypeInfo;
import eu.stratosphere.api.java.typeutils.TypeExtractor;
import eu.stratosphere.api.java.typeutils.TypeInformation;
//CHECKSTYLE.OFF: AvoidStarImport - Needed for TupleGenerator
import eu.stratosphere.api.java.tuple.*;
//CHECKSTYLE.ON: AvoidStarImport
/**
* A {@link DataSet} that is the result of a Cross transformation.
......@@ -71,6 +71,17 @@ public class CrossOperator<I1, I2, OUT>
// Builder classes for incremental construction
// --------------------------------------------------------------------------------------------
/**
* A Cross transformation that wraps pairs of crossed elements into {@link Tuple2}.<br/>
* It also represents the {@link DataSet} that is the result of a Cross transformation.
*
* @param <I1> The type of the first input DataSet of the Cross transformation.
* @param <I2> The type of the second input DataSet of the Cross transformation.
* @param <OUT> The type of the result of the Cross transformation.
*
* @see Tuple2
* @see DataSet
*/
public static final class DefaultCross<I1, I2> extends CrossOperator<I1, I2, Tuple2<I1, I2>> {
private final DataSet<I1> input1;
......@@ -88,6 +99,16 @@ public class CrossOperator<I1, I2, OUT>
this.input2 = input2;
}
/**
* Finalizes a Cross transformation by applying a {@link CrossFunction} to each pair of crossed elements.<br/>
* Each CrossFunction call returns exactly one element.
*
* @param function The CrossFunction that is called for each pair of crossed elements.
* @return An CrossOperator that represents the crossed result DataSet
*
* @see CrossFunction
* @see DataSet
*/
public <R> CrossOperator<I1, I2, R> with(CrossFunction<I1, I2, R> function) {
TypeInformation<R> returnType = TypeExtractor.getCrossReturnTypes(function, input1.getType(), input2.getType());
return new CrossOperator<I1, I2, R>(input1, input2, function, returnType);
......
......@@ -592,7 +592,8 @@ public abstract class JoinOperator<I1, I2, OUT> extends TwoInputUdfOperator<I1,
*
* @param fields The indexes of the Tuple fields of the first join DataSets that should be used as keys.
* @return An incomplete Join transformation.
* Call {@link JoinOperatorSetsPredicate#equalTo(int...)} to continue the Join.
* Call {@link JoinOperatorSetsPredicate#equalTo(int...)} or {@link JoinOperatorSetsPredicate#equalTo(KeySelector)}
* to continue the Join.
*
* @see Tuple
* @see DataSet
......@@ -601,20 +602,29 @@ public abstract class JoinOperator<I1, I2, OUT> extends TwoInputUdfOperator<I1,
return new JoinOperatorSetsPredicate(new Keys.FieldPositionKeys<I1>(fields, input1.getType()));
}
public <K extends Comparable<K>> JoinOperatorSetsPredicate where(KeySelector<I1, K> keyExtractor) {
return new JoinOperatorSetsPredicate(new Keys.SelectorFunctionKeys<I1, K>(keyExtractor, input1.getType()));
/**
* Continues a Join transformation and defines a {@link KeySelector} function for the first join {@link DataSet}.</br>
* The KeySelector function is called for each element of the first DataSet and extracts a single
* key value on which the DataSet is joined. </br>
*
* @param keySelector The KeySelector function which extracts the key values from the DataSet on which it is joined.
* @return An incomplete Join transformation.
* Call {@link JoinOperatorSetsPredicate#equalTo(int...)} or {@link JoinOperatorSetsPredicate#equalTo(KeySelector)}
* to continue the Join.
*
* @see KeySelector
* @see DataSet
*/
public <K extends Comparable<K>> JoinOperatorSetsPredicate where(KeySelector<I1, K> keySelector) {
return new JoinOperatorSetsPredicate(new Keys.SelectorFunctionKeys<I1, K>(keySelector, input1.getType()));
}
// public JoinOperatorSetsPredicate where(String keyExpression) {
// return new JoinOperatorSetsPredicate(new Keys.ExpressionKeys<I1>(keyExpression, input1.getType()));
// }
// ----------------------------------------------------------------------------------------
/**
* Intermediate step of a Join transformation. <br/>
* To continue the Join transformation, select the join key of the second input {@link DataSet} by calling
* {@link JoinOperatorSetsPredicate#equalTo(int...)}.
* {@link JoinOperatorSetsPredicate#equalTo(int...)} or {@link JoinOperatorSetsPredicate#equalTo(KeySelector)}.
*
*/
public class JoinOperatorSetsPredicate {
......@@ -642,7 +652,7 @@ public abstract class JoinOperator<I1, I2, OUT> extends TwoInputUdfOperator<I1,
* the element of the first input being the first field of the tuple and the element of the
* second input being the second field of the tuple.
*
* @param fields The indexes of the Tuple fields of the second join DataSets that should be used as keys.
* @param fields The indexes of the Tuple fields of the second join DataSet that should be used as keys.
* @return A DefaultJoin that represents the joined DataSet.
*/
public DefaultJoin<I1, I2> equalTo(int... fields) {
......@@ -658,17 +668,13 @@ public abstract class JoinOperator<I1, I2, OUT> extends TwoInputUdfOperator<I1,
* the element of the first input being the first field of the tuple and the element of the
* second input being the second field of the tuple.
*
* @param keyExtractor The KeySelector function which extracts the key values from the DataSet on which it is joined.
* @param keySelector The KeySelector function which extracts the key values from the second DataSet on which it is joined.
* @return A DefaultJoin that represents the joined DataSet.
*/
public <K> DefaultJoin<I1, I2> equalTo(KeySelector<I2, K> keyExtractor) {
return createJoinOperator(new Keys.SelectorFunctionKeys<I2, K>(keyExtractor, input2.getType()));
public <K> DefaultJoin<I1, I2> equalTo(KeySelector<I2, K> keySelector) {
return createJoinOperator(new Keys.SelectorFunctionKeys<I2, K>(keySelector, input2.getType()));
}
// public DefaultJoin<I1, I2> equalTo(String keyExpression) {
// return createJoinOperator(new Keys.ExpressionKeys<I2>(keyExpression, input2.getType()));
// }
protected DefaultJoin<I1, I2> createJoinOperator(Keys<I2> keys2) {
if (keys2 == null) {
throw new NullPointerException("The join keys may not be null.");
......
......@@ -82,6 +82,24 @@ public abstract class SingleInputUdfOperator<IN, OUT, O extends SingleInputUdfOp
return returnType;
}
/**
* Adds a constant-set annotation for the UDF.
*
* <p>
* Constant set annotations are used by the optimizer to infer the existence of data properties (sorted, partitioned, grouped).
* In certain cases, these annotations allow the optimizer to generate a more efficient execution plan which can lead to improved performance.
* Constant set annotations can only be specified if the second input and the output type of the UDF are of {@link Tuple} data types.
*
* <p>
* A constant-set annotation is a set of constant field specifications. The constant field specification String "4->3" specifies, that this UDF copies the fourth field of
* an input tuple to the third field of the output tuple. Field references are zero-indexed.
*
* <p>
* <b>NOTICE: Constant set annotations are optional, but if given need to be correct. Otherwise, the program might produce wrong results!</b>
*
* @param constantSet A list of constant field specification Strings.
* @return This operator with an annotated constant field set.
*/
public O withConstantSet(String... constantSet) {
SingleInputSemanticProperties props = SemanticPropUtil.getSemanticPropsSingleFromString(constantSet, null, null, this.getInputType(), this.getResultType());
this.setSemanticProperties(props);
......
......@@ -86,7 +86,22 @@ public abstract class TwoInputUdfOperator<IN1, IN2, OUT, O extends TwoInputUdfOp
}
/**
* Allows to give specifications about constant sets directly in the code. Null values are allowed for not specified sets.
* Adds a constant-set annotation for the first input of the UDF.
*
* <p>
* Constant set annotations are used by the optimizer to infer the existence of data properties (sorted, partitioned, grouped).
* In certain cases, these annotations allow the optimizer to generate a more efficient execution plan which can lead to improved performance.
* Constant set annotations can only be specified if the first input and the output type of the UDF are of {@link Tuple} data types.
*
* <p>
* A constant-set annotation is a set of constant field specifications. The constant field specification String "4->3" specifies, that this UDF copies the fourth field of
* an input tuple to the third field of the output tuple. Field references are zero-indexed.
*
* <p>
* <b>NOTICE: Constant set annotations are optional, but if given need to be correct. Otherwise, the program might produce wrong results!</b>
*
* @param constantSetFirst A list of constant field specification Strings for the first input.
* @return This operator with an annotated constant field set for the first input.
*/
@SuppressWarnings("unchecked")
public O withConstantSetFirst(String... constantSetFirst) {
......@@ -98,6 +113,24 @@ public abstract class TwoInputUdfOperator<IN1, IN2, OUT, O extends TwoInputUdfOp
return returnType;
}
/**
* Adds a constant-set annotation for the second input of the UDF.
*
* <p>
* Constant set annotations are used by the optimizer to infer the existence of data properties (sorted, partitioned, grouped).
* In certain cases, these annotations allow the optimizer to generate a more efficient execution plan which can lead to improved performance.
* Constant set annotations can only be specified if the second input and the output type of the UDF are of {@link Tuple} data types.
*
* <p>
* A constant-set annotation is a set of constant field specifications. The constant field specification String "4->3" specifies, that this UDF copies the fourth field of
* an input tuple to the third field of the output tuple. Field references are zero-indexed.
*
* <p>
* <b>NOTICE: Constant set annotations are optional, but if given need to be correct. Otherwise, the program might produce wrong results!</b>
*
* @param constantSetSecond A list of constant field specification Strings for the second input.
* @return This operator with an annotated constant field set for the second input.
*/
@SuppressWarnings("unchecked")
public O withConstantSetSecond(String... constantSetSecond) {
DualInputSemanticProperties dsp = SemanticPropUtil.getSemanticPropsDualFromString(null, constantSetSecond,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册