001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright (c) 2005-2017 The ConQAT Project                               |
004|                                                                          |
005| Licensed under the Apache License, Version 2.0 (the "License");          |
006| you may not use this file except in compliance with the License.         |
007| You may obtain a copy of the License at                                  |
008|                                                                          |
009|    http://www.apache.org/licenses/LICENSE-2.0                            |
010|                                                                          |
011| Unless required by applicable law or agreed to in writing, software      |
012| distributed under the License is distributed on an "AS IS" BASIS,        |
013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
014| See the License for the specific language governing permissions and      |
015| limitations under the License.                                           |
016|                                                                          |
017+-------------------------------------------------------------------------*/
018package eu.cqse.check.framework.postprocessor.cobol;
019
020import static eu.cqse.check.framework.shallowparser.SubTypeNames.ANONYMOUS_METHOD;
021import static eu.cqse.check.framework.shallowparser.framework.EShallowEntityType.META;
022import static eu.cqse.check.framework.shallowparser.framework.EShallowEntityType.METHOD;
023import static eu.cqse.check.framework.shallowparser.framework.EShallowEntityType.MODULE;
024import static eu.cqse.check.framework.shallowparser.framework.EShallowEntityType.STATEMENT;
025import static eu.cqse.check.framework.shallowparser.framework.EShallowEntityType.TYPE;
026import static eu.cqse.check.framework.shallowparser.languages.cobol.CobolShallowParser.PARAGRAPH_SUBTYPE_NAME;
027import static eu.cqse.check.framework.shallowparser.languages.cobol.CobolShallowParser.SECTION_SUBTYPE_NAME;
028
029import java.util.ArrayList;
030import java.util.EnumSet;
031import java.util.List;
032
033import org.conqat.lib.commons.collections.CollectionUtils;
034
035import eu.cqse.check.framework.scanner.IToken;
036import eu.cqse.check.framework.shallowparser.framework.EShallowEntityType;
037import eu.cqse.check.framework.shallowparser.framework.ShallowEntity;
038import eu.cqse.check.framework.util.tokens.TokenUtils;
039
040/**
041 * A Cobol post processor that transforms sections and paragraph statements into
042 * methods. Sections are checked first as priority before paragraphs.
043 */
044public class SectionParagraphToMethodProcessor {
045
046        /** List of non-comment tokens as seen by the Cobol Shallow Parser */
047        private final List<IToken> tokens;
048
049        /** Constructor. */
050        public SectionParagraphToMethodProcessor(List<IToken> tokens) {
051                this.tokens = CollectionUtils.filter(tokens, token -> !TokenUtils.isCommentToken(token));
052        }
053
054        /**
055         * Transforms a list of shallow entities from a parsing phase, post-processing
056         * them.
057         */
058        public List<ShallowEntity> postProcess(List<ShallowEntity> entities) {
059                List<ShallowEntity> result = new ArrayList<ShallowEntity>();
060                // Enterprise COBOL now supports method definition in the ID
061                // division so we do not include it here
062                EnumSet<EShallowEntityType> cobolProgramTypes = EnumSet.of(MODULE, TYPE);
063                for (ShallowEntity entity : entities) {
064                        ShallowEntity child;
065                        if (cobolProgramTypes.contains(entity.getType())) {
066                                child = processProgramTypeAndChildren(entity);
067                        } else {
068                                child = SectionParagraphToMethodProcessor.createCopy(entity, tokens);
069                        }
070
071                        child.setComplete();
072                        result.add(child);
073                }
074
075                fixEndTokenIndex(result);
076
077                return result;
078        }
079
080        /** Recursively fixes the end token index to include all children. */
081        private void fixEndTokenIndex(List<ShallowEntity> entities) {
082                for (ShallowEntity entity : entities) {
083                        if (entity.hasChildren()) {
084                                List<ShallowEntity> children = entity.getChildren();
085                                fixEndTokenIndex(children);
086                                entity.setEndTokenIndex(
087                                                Math.max(entity.getEndTokenIndex(), CollectionUtils.getLast(children).getEndTokenIndex()));
088                        }
089                }
090        }
091
092        /**
093         * Processes Cobol program types i.e. programs, classes, factories or objects
094         * and their children, depending on the presence or absence of section or
095         * paragraph statements.
096         */
097        private ShallowEntity processProgramTypeAndChildren(ShallowEntity programTypeEntity) {
098                List<ShallowEntity> children = programTypeEntity.getChildren();
099                List<Integer> indices = getIndicesForEntitiesToBecomeMethods(children, SECTION_SUBTYPE_NAME);
100                if (!indices.isEmpty()) {
101                        return makeSectionsParagraphsIntoMethods(programTypeEntity, indices);
102                }
103
104                indices = getIndicesForEntitiesToBecomeMethods(children, PARAGRAPH_SUBTYPE_NAME);
105                if (!indices.isEmpty()) {
106                        return makeSectionsParagraphsIntoMethods(programTypeEntity, indices);
107                }
108
109                return makeProgramTypeIntoMethod(programTypeEntity);
110        }
111
112        /**
113         * Retrieve the indices for sections or paragraphs as indicated in the sub-type
114         * parameter.
115         */
116        private static List<Integer> getIndicesForEntitiesToBecomeMethods(List<ShallowEntity> entities, String subType) {
117                List<Integer> indices = new ArrayList<Integer>();
118                for (int i = 0; i < entities.size(); ++i) {
119                        if (isStatementWithSubTypeAs(entities.get(i), subType)) {
120                                indices.add(i);
121                        }
122                }
123
124                return indices;
125        }
126
127        /**
128         * Transform the entities of a Cobol program, converting section or paragraph
129         * statements into methods and reorganizing other statements as children of the
130         * methods.
131         */
132        private ShallowEntity makeSectionsParagraphsIntoMethods(ShallowEntity programTypeEntity, List<Integer> indices) {
133                List<ShallowEntity> children = programTypeEntity.getChildren();
134                int entityToBecomeMethodIndex = 0;
135                int firstChildIndex = 0;
136                int lastChildIndex = indices.get(0) - 1;
137                ShallowEntity newModuleOrTypeEntity = transformProgramType(programTypeEntity, children, firstChildIndex,
138                                lastChildIndex);
139                while (entityToBecomeMethodIndex <= indices.size() - 1) {
140                        int index = indices.get(entityToBecomeMethodIndex);
141                        ShallowEntity entityToBecomeMethod = children.get(index);
142                        firstChildIndex = index + 1;
143                        entityToBecomeMethodIndex += 1;
144                        if (entityToBecomeMethodIndex == indices.size()) {
145                                // We have reached the last entity to become method
146                                lastChildIndex = children.size() - 1;
147                        } else {
148                                lastChildIndex = indices.get(entityToBecomeMethodIndex) - 1;
149                        }
150
151                        ShallowEntity transformedEntity = transformEntityAndAddChildren(entityToBecomeMethod, children,
152                                        firstChildIndex, lastChildIndex, true);
153                        transformedEntity.setComplete();
154                        newModuleOrTypeEntity.addChild(transformedEntity);
155                }
156
157                newModuleOrTypeEntity.setComplete();
158                return newModuleOrTypeEntity;
159        }
160
161        /**
162         * For a case where there are no sections and paragraphs, we transform the Cobol
163         * program, factory, object or class statement into a method block.
164         */
165        private ShallowEntity makeProgramTypeIntoMethod(ShallowEntity programTypeEntity) {
166                ShallowEntity changedProgramTypeEntity = SectionParagraphToMethodProcessor.createCopy(programTypeEntity,
167                                tokens);
168                if (programTypeEntity.getType() != METHOD) {
169                        // Enterprise Cobol method files will not come in here
170                        changedProgramTypeEntity = SectionParagraphToMethodProcessor.changeToMethodEntity(programTypeEntity,
171                                        tokens);
172                }
173
174                for (ShallowEntity entity : programTypeEntity.getChildren()) {
175                        changedProgramTypeEntity.addChild(transformChild(entity));
176                }
177
178                return changedProgramTypeEntity;
179        }
180
181        /**
182         * Recursively transform an entity and its children.
183         */
184        private ShallowEntity transformChild(ShallowEntity parent) {
185                ShallowEntity newParent = SectionParagraphToMethodProcessor.createCopy(parent, tokens);
186                newParent.setComplete();
187                parent.getChildren().forEach(child -> newParent.addChild(transformChild(child)));
188                return newParent;
189        }
190
191        /**
192         * Transform a Cobol program type and its children. If there are statements
193         * between the procedure division entity and first entity to be transformed to a
194         * method, they are made into an artificial method.
195         */
196        private ShallowEntity transformProgramType(ShallowEntity parent, List<ShallowEntity> programTypeChildren,
197                        int firstChildIndex, int lastChildIndex) {
198                ShallowEntity newParent = SectionParagraphToMethodProcessor.createCopy(parent, tokens);
199                int i;
200                for (i = firstChildIndex; i <= lastChildIndex; ++i) {
201                        ShallowEntity entity = programTypeChildren.get(i);
202                        newParent.addChild(transformChild(entity));
203                        if (isProcedureDivisionEntity(entity)) {
204                                // break out to decide if we create an artificial method for
205                                // subsequent entities before the first method in the procedure
206                                // division
207                                break;
208                        }
209                }
210
211                if (i == lastChildIndex) {
212                        return newParent;
213                }
214
215                i += 1;
216                ShallowEntity result = new ShallowEntity(METHOD, ANONYMOUS_METHOD, "#artificial#", tokens,
217                                programTypeChildren.get(i).getStartTokenIndex());
218                result.setEndTokenIndex(programTypeChildren.get(lastChildIndex).getEndTokenIndex());
219                ShallowEntity artificialMethod = result;
220                while (i <= lastChildIndex) {
221                        artificialMethod.addChild(transformChild(programTypeChildren.get(i)));
222                        i += 1;
223                }
224
225                artificialMethod.setComplete();
226                newParent.addChild(artificialMethod);
227                return newParent;
228        }
229
230        /**
231         * Returns true if an entity is a procedure division otherwise false.
232         */
233        private static boolean isProcedureDivisionEntity(ShallowEntity entity) {
234                return entity.getType() == META && entity.getSubtype().equals("division")
235                                && entity.getName().equals("procedure");
236        }
237
238        /**
239         * Transforms a given entity into a Cobol Shallow Entity and/or changing its
240         * shallow entity type into a method. For a collection of shallow entities under
241         * a Cobol program type, given entities A, B and C that will be transformed into
242         * methods, this procedure adds all entities between A and B as children of A
243         * while entities between B and C as children of B.
244         */
245        private ShallowEntity transformEntityAndAddChildren(ShallowEntity parent, List<ShallowEntity> programTypeChildren,
246                        int firstChildIndex, int lastChildIndex, boolean transformParentToMethod) {
247                ShallowEntity newParent = SectionParagraphToMethodProcessor.createCopy(parent, tokens);
248                if (transformParentToMethod) {
249                        newParent = SectionParagraphToMethodProcessor.changeToMethodEntity(parent, tokens);
250                }
251
252                for (int i = firstChildIndex; i <= lastChildIndex; ++i) {
253                        newParent.addChild(transformChild(programTypeChildren.get(i)));
254                }
255
256                if (transformParentToMethod) {
257                        newParent.setComplete();
258                }
259
260                return newParent;
261        }
262
263        /** Creates a copy of a shallow entity. */
264        private static ShallowEntity createCopy(ShallowEntity entity, List<IToken> tokens) {
265                ShallowEntity result = new ShallowEntity(entity.getType(), entity.getSubtype(), entity.getName(), tokens,
266                                entity.getStartTokenIndex());
267                result.setEndTokenIndex(entity.getEndTokenIndex());
268                return result;
269        }
270
271        /** Creates copy of a shallow entity with changed type "method". */
272        private static ShallowEntity changeToMethodEntity(ShallowEntity entity, List<IToken> tokens) {
273                ShallowEntity result = new ShallowEntity(METHOD, entity.getSubtype(), entity.getName(), tokens,
274                                entity.getStartTokenIndex());
275                result.setEndTokenIndex(entity.getEndTokenIndex());
276                return result;
277        }
278
279        /**
280         * Returns true if a given entity is a statement and has a sub-type as indicated
281         * in the parameter otherwise false.
282         */
283        private static boolean isStatementWithSubTypeAs(ShallowEntity entity, String subType) {
284                return entity.getSubtype() != null && entity.getSubtype().equals(subType) && entity.getType() == STATEMENT;
285        }
286}