class Pipeline extends EstimatorBase<Pipeline, PipelineModel> {

	@Override
	public PipelineModel fit(BatchOperator input) {
		int lastEstimatorIdx = getIndexOfLastEstimator();
		TransformerBase[] transformers = new TransformerBase[stages.size()];
		for (int i = 0; i < stages.size(); i++) {
			PipelineStageBase stage = stages.get(i);
			if (i <= lastEstimatorIdx) {
				if (stage instanceof EstimatorBase) {
					transformers[i] = ((EstimatorBase) stage).fit(input);
				} else if (stage instanceof TransformerBase) {
					transformers[i] = (TransformerBase) stage;
				}
				if (i < lastEstimatorIdx) {
					// temporarily disable lazy print transform results
					Boolean lazyPrintTransformDataEnabled = (Boolean) transformers[i].get(LAZY_PRINT_TRANSFORM_DATA_ENABLED);
					Boolean lazyPrintTransformStatEnabled = (Boolean) transformers[i].get(LAZY_PRINT_TRANSFORM_STAT_ENABLED);
					transformers[i].set(LAZY_PRINT_TRANSFORM_DATA_ENABLED, false);
					transformers[i].set(LAZY_PRINT_TRANSFORM_STAT_ENABLED, false);

					input = transformers[i].transform(input);

					transformers[i].set(LAZY_PRINT_TRANSFORM_DATA_ENABLED, lazyPrintTransformDataEnabled);
					transformers[i].set(LAZY_PRINT_TRANSFORM_STAT_ENABLED, lazyPrintTransformStatEnabled);
				}
			} else {
				// After lastEstimatorIdx, there're only Transformer stages, so it's safe to do type cast.
				transformers[i] = (TransformerBase) stage;
			}
		}
		return new PipelineModel(transformers).setMLEnvironmentId(input.getMLEnvironmentId());

	}

	public  Pipeline();
	public  Pipeline(Params params);
	public  Pipeline(PipelineStageBase<?>... stages);

}