/** * 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.horizondb.db.parser.builders; import io.horizondb.db.parser.BadHqlGrammarException; import io.horizondb.model.core.Projection; import io.horizondb.model.core.projections.DefaultProjection; import io.horizondb.model.core.projections.DefaultRecordTypeProjection; import io.horizondb.model.core.projections.NoopProjection; import io.horizondb.model.core.projections.NoopRecordTypeProjection; import io.horizondb.model.core.projections.RecordTypeProjection; import io.horizondb.model.schema.RecordTypeDefinition; import io.horizondb.model.schema.TimeSeriesDefinition; import java.util.ArrayList; import java.util.List; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; import static java.lang.String.format; /** * Builder for Projections. * */ final class ProjectionBuilder { private final List<String> selection; /** * Creates a new <code>ProjectionBuilder</code> for the specified selection * @param selection the selection */ public ProjectionBuilder(List<String> selection) { this.selection = selection; } public Projection build(TimeSeriesDefinition definition) throws BadHqlGrammarException { if (isSelectAll(this.selection)) { return new NoopProjection(); } ListMultimap<String, String> multimap = convertToMultiMap(this.selection); List<RecordTypeProjection> projections = new ArrayList<>(multimap.size()); try { for (String recordType: multimap.keySet()) { int index = definition.getRecordTypeIndex(recordType); List<String> fields = multimap.get(recordType); projections.add(toRecordTypeProjection(index, definition.getRecordType(index), fields)); } } catch (IllegalArgumentException e) { throw new BadHqlGrammarException(e.getMessage()); } return new DefaultProjection(projections); } /** * Validates that the specified fields exists within the specified record. * * @param typeDefinition the definition of the record type * @param fields the fields names * @throws BadHqlGrammarException if one of the field at list does not exists */ private static void validateFields(RecordTypeDefinition typeDefinition, List<String> fields) throws BadHqlGrammarException { for (int i = 0, m = fields.size(); i < m; i++) { String field = fields.get(i); if (typeDefinition.getFieldIndex(field) < 0) { throw new BadHqlGrammarException(format("the field %s does not exists within the record %s", field, typeDefinition.getName())); } } } /** * Converts the specified selection into a <code>MultiMap</code> * @return a MultiMap containing field names per record type name */ private static ListMultimap<String, String> convertToMultiMap(List<String> selection) { ListMultimap<String, String> multimap = ArrayListMultimap.create(); for (String expression : selection) { String[] elements = expression.split("\\."); String record = elements[0]; String field = elements[1]; multimap.put(record, field); } return multimap; } /** * Creates the <code>RecordTypeProjection</code> corresponding to the specified record type. * * @param index the record type index * @param definition the record type definition * @param fields the projected fields * @return the <code>RecordTypeProjection</code> corresponding to the specified record type. * @throws BadHqlGrammarException if the field names are not valid */ private static RecordTypeProjection toRecordTypeProjection(int index, RecordTypeDefinition definition, List<String> fields) throws BadHqlGrammarException { if (isSelectAll(fields)) { return new NoopRecordTypeProjection(index); } validateFields(definition, fields); return new DefaultRecordTypeProjection(index, fields); } /** * Checks if the specified selection select everything. * @return <code>true</code> if the specified selection select everything, <code>false</code> * otherwise. */ private static boolean isSelectAll(List<String> selection) { return selection.size() == 1 && selection.contains("*"); } }