001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright (c) 2009-2018 CQSE GmbH                                        |
004|                                                                          |
005+-------------------------------------------------------------------------*/
006package eu.cqse.check.framework.util.clike;
007
008import java.util.ArrayList;
009import java.util.List;
010
011import eu.cqse.check.framework.core.CheckException;
012import eu.cqse.check.framework.shallowparser.framework.EShallowEntityType;
013import eu.cqse.check.framework.shallowparser.framework.ShallowEntity;
014
015/**
016 * This class provides various helper methods to extract conditional block
017 * groups out of the abstract syntax tree or Lists of ShallowEntities.
018 */
019public class ConditionalBlockGroupExtractor {
020
021        /**
022         * Recursively checks the entity and its children for corresponding statements
023         * in conditional expressions. Use this for example on the Document Root coming
024         * from the AST to find all {@link ConditionalBlockGroupBase} in the Document.
025         *
026         * @param entity
027         *            the entity whose children are checked for if/else/else if or
028         *            switch/case statements
029         * @throws CheckException
030         */
031        public static List<ConditionalBlockGroupBase> findCorrespondingBlockGroups(ShallowEntity entity)
032                        throws CheckException {
033                List<ConditionalBlockGroupBase> groupedBlocks = new ArrayList<>();
034                EConditionalType blockType = ConditionalBlockUtils.getBlockTypeFromEntity(entity);
035
036                if (blockType != null) {
037                        switch (blockType) {
038                        case IF:
039                                groupedBlocks.add(createIfBlockGroup(entity));
040                                break;
041                        case SWITCH:
042                                groupedBlocks.add(createSwitchBlockGroup(entity));
043                                break;
044                        default:
045                                break;
046                        }
047                }
048
049                groupedBlocks.addAll(findCorrespondingBlockGroups(entity.getChildren()));
050
051                return groupedBlocks;
052        }
053
054        /**
055         * Recursively checks the given entities and their children for corresponding
056         * statements in conditional expressions.
057         * 
058         * @param entities
059         *            the entities whose children are checked for if/else/else if or
060         *            switch/case statements
061         * @throws CheckException
062         */
063        public static List<ConditionalBlockGroupBase> findCorrespondingBlockGroups(List<ShallowEntity> entities)
064                        throws CheckException {
065                List<ConditionalBlockGroupBase> groupedBlocks = new ArrayList<>();
066                if (entities.size() == 0) {
067                        return groupedBlocks;
068                }
069
070                ShallowEntity parent = entities.get(0).getParent();
071                for (ShallowEntity entity : entities) {
072                        ShallowEntity actualParent = entity.getParent();
073                        if (actualParent != parent) {
074                                String message = "The ShallowEntity: [" + entity.getSubtype().toUpperCase() + "] spanning lines ";
075                                message += entity.getStartLine() + " through " + entity.getEndLine();
076                                message += " has a different parent from its siblings";
077                                message += " .Expected parent: [" + parent.getSubtype() + "] spanning lines " + parent.getStartLine()
078                                                + " through " + parent.getEndLine();
079                                message += " .Actual parent: [" + actualParent.getSubtype() + "] spanning lines "
080                                                + actualParent.getStartLine() + " through " + actualParent.getEndLine();
081                                message += " File: " + entity.getAllTokens().get(0).getOriginId();
082                                throw new IllegalArgumentException(message);
083                        }
084
085                        List<ConditionalBlockGroupBase> childrenBlockGroups = findCorrespondingBlockGroups(entity);
086                        if (childrenBlockGroups.size() > 0) {
087                                groupedBlocks.addAll(childrenBlockGroups);
088                        }
089                }
090
091                return groupedBlocks;
092        }
093
094        /**
095         * Finds all corresponding else/ else if blocks for the given if statement
096         *
097         * @param ifEntity
098         *            the if statement for which the else/ else if blocks are searched
099         */
100        private static IfBlockGroup createIfBlockGroup(ShallowEntity ifEntity) {
101                List<ShallowEntity> neighboringEntities = ifEntity.getParent().getChildren();
102                int ifIndex = neighboringEntities.indexOf(ifEntity);
103
104                IfBlockGroup ifblockGroup = new IfBlockGroup();
105
106                ConditionalBlock ifBlock = new ConditionalBlock(ConditionalBlockUtils.getBlockTypeFromEntity(ifEntity),
107                                ConditionalBlockUtils.extractCondition(ifEntity), ifEntity.getChildren());
108                ifblockGroup.getCorrespondingBlocks().add(ifBlock);
109
110                for (int nextIndex = ifIndex + 1; nextIndex < neighboringEntities.size(); nextIndex++) {
111                        ShallowEntity nextEntity = neighboringEntities.get(nextIndex);
112                        EConditionalType blockType = ConditionalBlockUtils.getBlockTypeFromEntity(nextEntity);
113                        if (ConditionalBlockUtils.BLOCKS_CORRESPONDING_TO_IF.contains(blockType)) {
114                                if (blockType == EConditionalType.ELSE_IF) {
115                                        ifblockGroup.getCorrespondingBlocks().add(new ConditionalBlock(blockType,
116                                                        ConditionalBlockUtils.extractCondition(nextEntity), nextEntity.getChildren()));
117                                } else if (blockType == EConditionalType.ELSE) {
118                                        ifblockGroup.getCorrespondingBlocks()
119                                                        .add(new ConditionalBlock(blockType, nextEntity.getChildren()));
120                                        break;
121                                }
122                        } else {
123                                break;
124                        }
125                }
126
127                return ifblockGroup;
128        }
129
130        /**
131         * Finds all corresponding case blocks for the given switch statement
132         *
133         * @param switchEntity
134         *            the switch statement for which the cases are searched
135         */
136        private static SwitchBlockGroup createSwitchBlockGroup(ShallowEntity switchEntity) {
137                List<ShallowEntity> metas = findMetaStatements(switchEntity);
138                List<ShallowEntity> innerStatements = switchEntity.getChildren();
139
140                SwitchBlockGroup switchBlockGroup = new SwitchBlockGroup();
141                switchBlockGroup.setSurroundingEntity(switchEntity);
142
143                for (int i = 0; i < metas.size(); i++) {
144                        ShallowEntity currentMeta = metas.get(i);
145
146                        int currentMetaParentIndex = innerStatements.indexOf(currentMeta);
147                        int nextMetaParentIndex;
148                        if (i + 1 < metas.size()) {
149                                nextMetaParentIndex = innerStatements.indexOf(metas.get(i + 1));
150                        } else {
151                                nextMetaParentIndex = innerStatements.size();
152                        }
153
154                        List<ShallowEntity> metaStatements = innerStatements.subList(currentMetaParentIndex + 1,
155                                        nextMetaParentIndex);
156
157                        if (metaStatements.isEmpty()) {
158                                // empty statements are not relevant for this check (e.g. fallthrough case
159                                // statement)
160                                continue;
161                        }
162
163                        EConditionalType blockType = ConditionalBlockUtils.getBlockTypeFromEntity(currentMeta);
164
165                        if (blockType == EConditionalType.CASE) {
166                                switchBlockGroup.getCorrespondingBlocks().add(new ConditionalBlock(blockType,
167                                                ConditionalBlockUtils.extractCondition(currentMeta), metaStatements));
168                        } else {
169                                switchBlockGroup.getCorrespondingBlocks().add(new ConditionalBlock(blockType, metaStatements));
170                        }
171                }
172
173                return switchBlockGroup;
174        }
175
176        /**
177         * Searches for the meta statements inside the switch case construct. Meta
178         * statements are case and default statements.
179         */
180        private static List<ShallowEntity> findMetaStatements(ShallowEntity entity) {
181                List<ShallowEntity> metas = new ArrayList<>();
182                for (ShallowEntity innerStatement : entity.getChildren()) {
183                        if (innerStatement.getType() == EShallowEntityType.META) {
184                                metas.add(innerStatement);
185                        }
186                }
187                return metas;
188        }
189}