/** * Substitute exprs of the form "<number>" with the corresponding expressions from select list * * @param exprs * @param errorPrefix * @throws AnalysisException */ @Override protected void substituteOrdinals(List<Expr> exprs, String errorPrefix) throws AnalysisException { // substitute ordinals ListIterator<Expr> i = exprs.listIterator(); while (i.hasNext()) { Expr expr = i.next(); if (!(expr instanceof IntLiteral)) { continue; } long pos = ((IntLiteral) expr).getValue(); if (pos < 1) { throw new AnalysisException(errorPrefix + ": ordinal must be >= 1: " + expr.toSql()); } if (pos > selectList.getItems().size()) { throw new AnalysisException( errorPrefix + ": ordinal exceeds number of items in select list: " + expr.toSql()); } if (selectList.getItems().get((int) pos - 1).isStar()) { throw new AnalysisException( errorPrefix + ": ordinal refers to '*' in select list: " + expr.toSql()); } // create copy to protect against accidentally shared state i.set(selectList.getItems().get((int) pos - 1).getExpr().clone(null)); } }
/** Returns the SQL string corresponding to this SelectStmt. */ @Override public String toSql() { // Return the SQL string before inline-view expression substitution. if (sqlString_ != null) return sqlString_; StringBuilder strBuilder = new StringBuilder(); if (withClause_ != null) { strBuilder.append(withClause_.toSql()); strBuilder.append(" "); } // Select list strBuilder.append("SELECT "); if (selectList_.isDistinct()) { strBuilder.append("DISTINCT "); } for (int i = 0; i < selectList_.getItems().size(); ++i) { strBuilder.append(selectList_.getItems().get(i).toSql()); strBuilder.append((i + 1 != selectList_.getItems().size()) ? ", " : ""); } // From clause if (!tableRefs_.isEmpty()) { strBuilder.append(" FROM "); for (int i = 0; i < tableRefs_.size(); ++i) { strBuilder.append(tableRefs_.get(i).toSql()); } } // Where clause if (whereClause_ != null) { strBuilder.append(" WHERE "); strBuilder.append(whereClause_.toSql()); } // Group By clause if (groupingExprs_ != null) { strBuilder.append(" GROUP BY "); for (int i = 0; i < groupingExprs_.size(); ++i) { strBuilder.append(groupingExprs_.get(i).toSql()); strBuilder.append((i + 1 != groupingExprs_.size()) ? ", " : ""); } } // Having clause if (havingClause_ != null) { strBuilder.append(" HAVING "); strBuilder.append(havingClause_.toSql()); } // Order By clause if (orderByElements_ != null) { strBuilder.append(" ORDER BY "); for (int i = 0; i < orderByElements_.size(); ++i) { strBuilder.append(orderByElements_.get(i).toSql()); strBuilder.append((i + 1 != orderByElements_.size()) ? ", " : ""); } } // Limit clause. strBuilder.append(limitElement_.toSql()); return strBuilder.toString(); }
@Override public String toSql() { StringBuilder strBuilder = new StringBuilder(); // Select list strBuilder.append("SELECT "); if (selectList.isDistinct()) { strBuilder.append("DISTINCT "); } for (int i = 0; i < selectList.getItems().size(); ++i) { strBuilder.append(selectList.getItems().get(i).toSql()); strBuilder.append((i + 1 != selectList.getItems().size()) ? ", " : ""); } // From clause if (!tableRefs.isEmpty()) { strBuilder.append(" FROM "); for (int i = 0; i < tableRefs.size(); ++i) { strBuilder.append(tableRefs.get(i).toSql()); } } // Where clause if (whereClause != null) { strBuilder.append(" WHERE "); strBuilder.append(whereClause.toSql()); } // Group By clause if (groupingExprs != null) { strBuilder.append(" GROUP BY "); for (int i = 0; i < groupingExprs.size(); ++i) { strBuilder.append(groupingExprs.get(i).toSql()); strBuilder.append((i + 1 != groupingExprs.size()) ? ", " : ""); } } // Having clause if (havingClause != null) { strBuilder.append(" HAVING "); strBuilder.append(havingClause.toSql()); } // Order By clause if (orderByElements != null) { strBuilder.append(" ORDER BY "); for (int i = 0; i < orderByElements.size(); ++i) { strBuilder.append(orderByElements.get(i).getExpr().toSql()); strBuilder.append((sortInfo.getIsAscOrder().get(i)) ? " ASC" : " DESC"); strBuilder.append((i + 1 != orderByElements.size()) ? ", " : ""); } } // Limit clause. if (hasLimitClause()) { strBuilder.append(" LIMIT "); strBuilder.append(limit); } return strBuilder.toString(); }
private void createViewer() { String[] h = host.getText().split(":"); String hostName = h[0]; String portText = h[1]; String userText = user.getText(); String passText = new String(password.getPassword()); if (hostName != null && portText != null) { SelectList sl = new SelectList(hostName, new Integer(portText).intValue(), userText, passText); sl.draw(); } }
@Override public void draw(SpriteBatch batch, float parentAlpha) { Drawable background; if (list != null && list.getParent() != null && style.backgroundOpen != null) background = style.backgroundOpen; else if (clickListener.isOver() && style.backgroundOver != null) background = style.backgroundOver; else background = style.background; final BitmapFont font = style.font; final Color fontColor = style.fontColor; Color color = getColor(); float x = getX(); float y = getY(); float width = getWidth(); float height = getHeight(); batch.setColor(color.r, color.g, color.b, color.a * parentAlpha); background.draw(batch, x, y, width, height); if (items.length > 0) { float availableWidth = width - background.getLeftWidth() - background.getRightWidth(); int numGlyphs = font.computeVisibleGlyphs( items[selectedIndex], 0, items[selectedIndex].length(), availableWidth); bounds.set(font.getBounds(items[selectedIndex])); height -= background.getBottomHeight() + background.getTopHeight(); float textY = (int) (height / 2 + background.getBottomHeight() + bounds.height / 2); font.setColor(fontColor.r, fontColor.g, fontColor.b, fontColor.a * parentAlpha); font.draw( batch, items[selectedIndex], x + background.getLeftWidth(), y + textY, 0, numGlyphs); } // calculate screen coords where list should be displayed getStage().toScreenCoordinates(screenCoords.set(x, y), batch.getTransformMatrix()); }
@Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { // start out with table refs to establish aliases TableRef leftTblRef = null; // the one to the left of tblRef for (TableRef tblRef : tableRefs) { tblRef.setLeftTblRef(leftTblRef); tblRef.analyze(analyzer); leftTblRef = tblRef; } // populate selectListExprs, aliasSMap, and colNames for (SelectListItem item : selectList.getItems()) { if (item.isStar()) { TableName tblName = item.getTblName(); if (tblName == null) { expandStar(analyzer); } else { expandStar(analyzer, tblName); } } else { resultExprs.add(item.getExpr()); SlotRef aliasRef = new SlotRef(null, item.toColumnLabel()); if (aliasSMap.lhs.contains(aliasRef)) { // If we have already seen this alias, it refers to more than one column and // therefore is ambiguous. ambiguousAliasList.add(aliasRef); } aliasSMap.lhs.add(aliasRef); aliasSMap.rhs.add(item.getExpr().clone(null)); colLabels.add(item.toColumnLabel()); } } // analyze selectListExprs Expr.analyze(resultExprs, analyzer); if (whereClause != null) { whereClause.analyze(analyzer); if (whereClause.contains(AggregateExpr.class)) { throw new AnalysisException("aggregation function not allowed in WHERE clause"); } whereClause.checkReturnsBool("WHERE clause", false); analyzer.registerConjuncts(whereClause, null, true); } createSortInfo(analyzer); analyzeAggregation(analyzer); // Substitute expressions to the underlying inline view expressions substituteInlineViewExprs(analyzer); if (aggInfo != null) { LOG.debug("post-analysis " + aggInfo.debugString()); } }
/** Create aggInfo for the given grouping and agg exprs. */ private void createAggInfo( ArrayList<Expr> groupingExprs, ArrayList<AggregateExpr> aggExprs, Analyzer analyzer) throws AnalysisException, InternalException { if (selectList.isDistinct()) { // Create aggInfo for SELECT DISTINCT ... stmt: // - all select list items turn into grouping exprs // - there are no aggregate exprs Preconditions.checkState(groupingExprs.isEmpty()); Preconditions.checkState(aggExprs.isEmpty()); aggInfo = AggregateInfo.create(Expr.cloneList(resultExprs, null), null, null, analyzer); } else { aggInfo = AggregateInfo.create(groupingExprs, aggExprs, null, analyzer); } }
@Override public QueryStmt clone() { SelectStmt selectClone = new SelectStmt( selectList_.clone(), cloneTableRefs(), (whereClause_ != null) ? whereClause_.clone(null) : null, (groupingExprs_ != null) ? Expr.cloneList(groupingExprs_) : null, (havingClause_ != null) ? havingClause_.clone(null) : null, cloneOrderByElements(), (limitElement_ != null) ? limitElement_.clone(null) : null); selectClone.setWithClause(cloneWithClause()); return selectClone; }
/** * _more_ * * @param desc _more_ * @param dqc _more_ * @param selService _more_ * @param service _more_ * @param selStation _more_ * @param selRegion _more_ * @param selTime _more_ */ private DqcRadarDatasetCollection( String desc, QueryCapability dqc, SelectService selService, SelectService.ServiceChoice service, SelectStation selStation, SelectGeoRegion selRegion, SelectList selTime) { super(); // this.ds = ds; this.desc = desc; this.dqc = dqc; this.selService = selService; this.selStation = selStation; this.selRegion = selRegion; this.selTime = selTime; this.service = service; ArrayList stationList = selStation.getStations(); stations = new HashMap(stationList.size()); for (int i = 0; i < stationList.size(); i++) { thredds.catalog.query.Station station = (thredds.catalog.query.Station) stationList.get(i); // DqcRadarStation dd = new DqcRadarStation(station); stations.put(station.getValue(), station); } ArrayList timeList = selTime.getChoices(); relTimesList = new HashMap(timeList.size()); for (int i = 0; i < timeList.size(); i++) { thredds.catalog.query.Choice tt = (thredds.catalog.query.Choice) timeList.get(i); relTimesList.put(tt.getValue(), tt); } String ql = dqc.getQuery().getUriResolved().toString(); startDate = new Date(); endDate = new Date(); try { timeUnit = new DateUnit("hours since 1991-01-01T00:00"); } catch (Exception e) { e.printStackTrace(); } }
/** * Getting data relative time list for a single radar station. * * @param stn radar station name * @return list of relative times * @throws IOException java io exception */ private List queryRadarStationRTimes(String stn) throws IOException { return selTime.getChoices(); }
/** * Analyze aggregation-relevant components of the select block (Group By clause, select list, * Order By clause), substitute AVG with SUM/COUNT, create the AggregationInfo, including the agg * output tuple, and transform all post-agg exprs given AggregationInfo's smap. * * @param analyzer * @throws AnalysisException */ private void analyzeAggregation(Analyzer analyzer) throws AnalysisException, InternalException { if (groupingExprs == null && !selectList.isDistinct() && !Expr.contains(resultExprs, AggregateExpr.class)) { // we're not computing aggregates return; } // If we're computing an aggregate, we must have a FROM clause. if (tableRefs.size() == 0) { throw new AnalysisException("aggregation without a FROM clause is not allowed"); } if ((groupingExprs != null || Expr.contains(resultExprs, AggregateExpr.class)) && selectList.isDistinct()) { throw new AnalysisException( "cannot combine SELECT DISTINCT with aggregate functions or GROUP BY"); } // disallow '*' and explicit GROUP BY (we can't group by '*', and if you need to // name all star-expanded cols in the group by clause you might as well do it // in the select list) if (groupingExprs != null) { for (SelectListItem item : selectList.getItems()) { if (item.isStar()) { throw new AnalysisException( "cannot combine '*' in select list with GROUP BY: " + item.toSql()); } } } // analyze grouping exprs ArrayList<Expr> groupingExprsCopy = Lists.newArrayList(); if (groupingExprs != null) { // make a deep copy here, we don't want to modify the original // exprs during analysis (in case we need to print them later) groupingExprsCopy = Expr.cloneList(groupingExprs, null); substituteOrdinals(groupingExprsCopy, "GROUP BY"); Expr ambiguousAlias = getFirstAmbiguousAlias(groupingExprsCopy); if (ambiguousAlias != null) { throw new AnalysisException( "Column " + ambiguousAlias.toSql() + " in group by clause is ambiguous"); } Expr.substituteList(groupingExprsCopy, aliasSMap); for (int i = 0; i < groupingExprsCopy.size(); ++i) { groupingExprsCopy.get(i).analyze(analyzer); if (groupingExprsCopy.get(i).contains(AggregateExpr.class)) { // reference the original expr in the error msg throw new AnalysisException( "GROUP BY expression must not contain aggregate functions: " + groupingExprs.get(i).toSql()); } } } // analyze having clause if (havingClause != null) { // substitute aliases in place (ordinals not allowed in having clause) havingPred = havingClause.clone(aliasSMap); havingPred.analyze(analyzer); havingPred.checkReturnsBool("HAVING clause", true); analyzer.registerConjuncts(havingPred, null, false); } List<Expr> orderingExprs = null; if (sortInfo != null) { orderingExprs = sortInfo.getOrderingExprs(); } ArrayList<AggregateExpr> aggExprs = collectAggExprs(); Expr.SubstitutionMap avgSMap = createAvgSMap(aggExprs, analyzer); // substitute AVG before constructing AggregateInfo Expr.substituteList(aggExprs, avgSMap); ArrayList<AggregateExpr> nonAvgAggExprs = Lists.newArrayList(); Expr.collectList(aggExprs, AggregateExpr.class, nonAvgAggExprs); aggExprs = nonAvgAggExprs; createAggInfo(groupingExprsCopy, aggExprs, analyzer); // combine avg smap with the one that produces the final agg output AggregateInfo finalAggInfo = aggInfo.getSecondPhaseDistinctAggInfo() != null ? aggInfo.getSecondPhaseDistinctAggInfo() : aggInfo; Expr.SubstitutionMap combinedSMap = Expr.SubstitutionMap.combine(avgSMap, finalAggInfo.getSMap()); LOG.debug("combined smap: " + combinedSMap.debugString()); // change select list, having and ordering exprs to point to agg output Expr.substituteList(resultExprs, combinedSMap); LOG.debug("post-agg selectListExprs: " + Expr.debugString(resultExprs)); if (havingPred != null) { havingPred = havingPred.substitute(combinedSMap); LOG.debug("post-agg havingPred: " + havingPred.debugString()); } Expr.substituteList(orderingExprs, combinedSMap); LOG.debug("post-agg orderingExprs: " + Expr.debugString(orderingExprs)); // check that all post-agg exprs point to agg output for (int i = 0; i < selectList.getItems().size(); ++i) { if (!resultExprs.get(i).isBound(finalAggInfo.getAggTupleId())) { throw new AnalysisException( "select list expression not produced by aggregation output " + "(missing from GROUP BY clause?): " + selectList.getItems().get(i).getExpr().toSql()); } } if (orderByElements != null) { for (int i = 0; i < orderByElements.size(); ++i) { if (!orderingExprs.get(i).isBound(finalAggInfo.getAggTupleId())) { throw new AnalysisException( "ORDER BY expression not produced by aggregation output " + "(missing from GROUP BY clause?): " + orderByElements.get(i).getExpr().toSql()); } } } if (havingPred != null) { if (!havingPred.isBound(finalAggInfo.getAggTupleId())) { throw new AnalysisException( "HAVING clause not produced by aggregation output " + "(missing from GROUP BY clause?): " + havingClause.toSql()); } } }
/** * Analyze aggregation-relevant components of the select block (Group By clause, select list, * Order By clause), substitute AVG with SUM/COUNT, create the AggregationInfo, including the agg * output tuple, and transform all post-agg exprs given AggregationInfo's smap. */ private void analyzeAggregation(Analyzer analyzer) throws AnalysisException, AuthorizationException { if (groupingExprs_ == null && !selectList_.isDistinct() && !TreeNode.contains(resultExprs_, Expr.isAggregatePredicate())) { // we're not computing aggregates return; } // If we're computing an aggregate, we must have a FROM clause. if (tableRefs_.size() == 0) { throw new AnalysisException("aggregation without a FROM clause is not allowed"); } if ((groupingExprs_ != null || TreeNode.contains(resultExprs_, Expr.isAggregatePredicate())) && selectList_.isDistinct()) { throw new AnalysisException( "cannot combine SELECT DISTINCT with aggregate functions or GROUP BY"); } // disallow '*' and explicit GROUP BY (we can't group by '*', and if you need to // name all star-expanded cols in the group by clause you might as well do it // in the select list) if (groupingExprs_ != null) { for (SelectListItem item : selectList_.getItems()) { if (item.isStar()) { throw new AnalysisException( "cannot combine '*' in select list with GROUP BY: " + item.toSql()); } } } // analyze grouping exprs ArrayList<Expr> groupingExprsCopy = Lists.newArrayList(); if (groupingExprs_ != null) { // make a deep copy here, we don't want to modify the original // exprs during analysis (in case we need to print them later) groupingExprsCopy = Expr.cloneList(groupingExprs_); substituteOrdinals(groupingExprsCopy, "GROUP BY"); Expr ambiguousAlias = getFirstAmbiguousAlias(groupingExprsCopy); if (ambiguousAlias != null) { throw new AnalysisException( "Column " + ambiguousAlias.toSql() + " in group by clause is ambiguous"); } Expr.substituteList(groupingExprsCopy, aliasSmap_); for (int i = 0; i < groupingExprsCopy.size(); ++i) { groupingExprsCopy.get(i).analyze(analyzer); if (groupingExprsCopy.get(i).contains(Expr.isAggregatePredicate())) { // reference the original expr in the error msg throw new AnalysisException( "GROUP BY expression must not contain aggregate functions: " + groupingExprs_.get(i).toSql()); } } } // analyze having clause if (havingClause_ != null) { // substitute aliases in place (ordinals not allowed in having clause) havingPred_ = havingClause_.clone(aliasSmap_); havingPred_.analyze(analyzer); havingPred_.checkReturnsBool("HAVING clause", true); } List<Expr> orderingExprs = null; if (sortInfo_ != null) orderingExprs = sortInfo_.getOrderingExprs(); // Collect the aggregate expressions from the SELECT, HAVING and ORDER BY clauses // of this statement. ArrayList<FunctionCallExpr> aggExprs = Lists.newArrayList(); TreeNode.collect(resultExprs_, Expr.isAggregatePredicate(), aggExprs); if (havingPred_ != null) { havingPred_.collect(Expr.isAggregatePredicate(), aggExprs); } if (sortInfo_ != null) { TreeNode.collect(sortInfo_.getOrderingExprs(), Expr.isAggregatePredicate(), aggExprs); } // substitute AVG before constructing AggregateInfo Expr.SubstitutionMap avgSMap = createAvgSMap(aggExprs, analyzer); ArrayList<Expr> substitutedAggs = Expr.cloneList(aggExprs, avgSMap); ArrayList<FunctionCallExpr> nonAvgAggExprs = Lists.newArrayList(); TreeNode.collect(substitutedAggs, Expr.isAggregatePredicate(), nonAvgAggExprs); // When DISTINCT aggregates are present, non-distinct (i.e. ALL) aggregates are // evaluated in two phases (see AggregateInfo for more details). In particular, // COUNT(c) in "SELECT COUNT(c), AGG(DISTINCT d) from R" is transformed to // "SELECT SUM(cnt) FROM (SELECT COUNT(c) as cnt from R group by d ) S". // Since a group-by expression is added to the inner query it returns no rows if // R is empty, in which case the SUM of COUNTs will return NULL. // However the original COUNT(c) should have returned 0 instead of NULL in this case. // Therefore, COUNT([ALL]) is transformed into zeroifnull(COUNT([ALL]) if // i) There is no GROUP-BY clause, and // ii) Other DISTINCT aggregates are present. Expr.SubstitutionMap countAllMap = createCountAllMap(nonAvgAggExprs, analyzer); substitutedAggs = Expr.cloneList(nonAvgAggExprs, countAllMap); aggExprs.clear(); TreeNode.collect(substitutedAggs, Expr.isAggregatePredicate(), aggExprs); try { createAggInfo(groupingExprsCopy, aggExprs, analyzer); } catch (InternalException e) { throw new AnalysisException(e.getMessage(), e); } // combine avg smap with the one that produces the final agg output AggregateInfo finalAggInfo = aggInfo_.getSecondPhaseDistinctAggInfo() != null ? aggInfo_.getSecondPhaseDistinctAggInfo() : aggInfo_; Expr.SubstitutionMap combinedSMap = Expr.SubstitutionMap.compose( Expr.SubstitutionMap.compose(avgSMap, countAllMap), finalAggInfo.getSMap()); LOG.debug("combined smap: " + combinedSMap.debugString()); // change select list, having and ordering exprs to point to agg output Expr.substituteList(resultExprs_, combinedSMap); LOG.debug("post-agg selectListExprs: " + Expr.debugString(resultExprs_)); if (havingPred_ != null) { havingPred_ = havingPred_.substitute(combinedSMap); analyzer.registerConjuncts(havingPred_, null, false); LOG.debug("post-agg havingPred: " + havingPred_.debugString()); } Expr.substituteList(orderingExprs, combinedSMap); LOG.debug("post-agg orderingExprs: " + Expr.debugString(orderingExprs)); // check that all post-agg exprs point to agg output for (int i = 0; i < selectList_.getItems().size(); ++i) { if (!resultExprs_.get(i).isBound(finalAggInfo.getAggTupleId())) { throw new AnalysisException( "select list expression not produced by aggregation output " + "(missing from GROUP BY clause?): " + selectList_.getItems().get(i).getExpr().toSql()); } } if (orderByElements_ != null) { for (int i = 0; i < orderByElements_.size(); ++i) { if (!orderingExprs.get(i).isBound(finalAggInfo.getAggTupleId())) { throw new AnalysisException( "ORDER BY expression not produced by aggregation output " + "(missing from GROUP BY clause?): " + orderByElements_.get(i).getExpr().toSql()); } } } if (havingPred_ != null) { if (!havingPred_.isBound(finalAggInfo.getAggTupleId())) { throw new AnalysisException( "HAVING clause not produced by aggregation output " + "(missing from GROUP BY clause?): " + havingClause_.toSql()); } } }
/** Creates resultExprs and baseTblResultExprs. */ @Override public void analyze(Analyzer analyzer) throws AnalysisException, AuthorizationException { super.analyze(analyzer); // Replace BaseTableRefs with ViewRefs. substituteViews(analyzer, tableRefs_); // start out with table refs to establish aliases TableRef leftTblRef = null; // the one to the left of tblRef for (TableRef tblRef : tableRefs_) { tblRef.setLeftTblRef(leftTblRef); try { tblRef.analyze(analyzer); } catch (AnalysisException e) { // Only re-throw the exception if no tables are missing. if (analyzer.getMissingTbls().isEmpty()) throw e; } leftTblRef = tblRef; } // All tableRefs have been analyzed, but at least one table was found missing. // There is no reason to proceed with analysis past this point. if (!analyzer.getMissingTbls().isEmpty()) { throw new AnalysisException("Found missing tables. Aborting analysis."); } // populate selectListExprs, aliasSMap, and colNames for (int i = 0; i < selectList_.getItems().size(); ++i) { SelectListItem item = selectList_.getItems().get(i); if (item.isStar()) { TableName tblName = item.getTblName(); if (tblName == null) { expandStar(analyzer); } else { expandStar(analyzer, tblName); } } else { // Analyze the resultExpr before generating a label to ensure enforcement // of expr child and depth limits (toColumn() label may call toSql()). item.getExpr().analyze(analyzer); resultExprs_.add(item.getExpr()); String label = item.toColumnLabel(i, analyzer.useHiveColLabels()); SlotRef aliasRef = new SlotRef(null, label); if (aliasSmap_.containsMappingFor(aliasRef)) { // If we have already seen this alias, it refers to more than one column and // therefore is ambiguous. ambiguousAliasList_.add(aliasRef); } aliasSmap_.addMapping(aliasRef, item.getExpr().clone(null)); colLabels_.add(label); } } if (whereClause_ != null) { whereClause_.analyze(analyzer); if (whereClause_.contains(Expr.isAggregatePredicate())) { throw new AnalysisException("aggregate function not allowed in WHERE clause"); } whereClause_.checkReturnsBool("WHERE clause", false); analyzer.registerConjuncts(whereClause_, null, true); } createSortInfo(analyzer); analyzeAggregation(analyzer); // Remember the SQL string before inline-view expression substitution. sqlString_ = toSql(); resolveInlineViewRefs(analyzer); if (aggInfo_ != null) LOG.debug("post-analysis " + aggInfo_.debugString()); }
public void hideList() { if (list.getParent() == null) return; list.addAction(sequence(fadeOut(0.15f, Interpolation.fade), removeActor())); }