001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright 2005-2011 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+-------------------------------------------------------------------------*/
017package eu.cqse.check.framework.shallowparser.util;
018
019import java.util.List;
020
021import org.conqat.lib.commons.assertion.CCSMAssert;
022import org.conqat.lib.commons.collections.CollectionUtils;
023import org.conqat.lib.commons.collections.UnmodifiableList;
024
025import eu.cqse.check.framework.scanner.ELanguage;
026import eu.cqse.check.framework.scanner.ETokenType;
027import eu.cqse.check.framework.scanner.IToken;
028import eu.cqse.check.framework.shallowparser.framework.EShallowEntityType;
029import eu.cqse.check.framework.shallowparser.framework.ShallowEntity;
030
031/**
032 * Utility methods that can be used to extract the fully qualified names of
033 * elements in the shallow AST.
034 */
035public class ShallowParsedNameUtils {
036
037        /** C++ scope separator */
038        private static final String CPP_SCOPE = "::";
039
040        /**
041         * Returns the fully qualified name of the programming construct the given
042         * entity represents. This includes surrounding classes and namespaces. This
043         * method employs various language-specific heuristics and thus probably
044         * needs to be extended for new languages after extending the parser.
045         * 
046         * @param entity
047         *            the entity must be of type MODULE, TYPE, METHOD, or ATTRIBUTE.
048         */
049        public static String getFullyQualifiedName(ShallowEntity entity) {
050                return getFullyQualifiedName(entity, determineLanguage(entity));
051        }
052
053        /**
054         * Returns the fully qualified name of the programming construct the given
055         * entity represents. This includes surrounding classes and namespaces. This
056         * method employs various language-specific heuristics and thus probably
057         * needs to be extended for new languages after extending the parser.
058         * 
059         * @param entity
060         *            the entity must be of type MODULE, TYPE, METHOD, or ATTRIBUTE.
061         */
062        public static String getFullyQualifiedName(ShallowEntity entity, ELanguage language) {
063
064                EShallowEntityType type = entity.getType();
065                CCSMAssert.isTrue(
066                                type == EShallowEntityType.MODULE || type == EShallowEntityType.TYPE
067                                                || type == EShallowEntityType.METHOD || type == EShallowEntityType.ATTRIBUTE,
068                                "May only pass MODULE, TYPE, METHOD, or ATTRIBUTE!");
069
070                String name = entity.getName();
071
072                if (language == ELanguage.CPP) {
073                        if (type == EShallowEntityType.METHOD || type == EShallowEntityType.ATTRIBUTE) {
074                                name = extractCppMethodOrAttributeName(entity);
075                        }
076                }
077
078                // the previous code dealt with local name expansion, while the final
079                // call adds global expansion by adding the names of parent types and
080                // modules (think surrounding class).
081                return expandParentNames(name, entity, language);
082        }
083
084        /**
085         * Returns the string obtained by prefixing the given name by the names of
086         * all TYPE/MODULE parent entities using the correct separator for the
087         * language.
088         */
089        private static String expandParentNames(String name, ShallowEntity entity, ELanguage language) {
090
091                String separator;
092                switch (language) {
093                case CPP:
094                        separator = CPP_SCOPE;
095                        break;
096                case JAVA:
097                case CS:
098                        separator = ".";
099                        break;
100                default:
101                        // we use a non-standard default separator for unknown languages to
102                        // encourage updating the code but not hindering experiments on new
103                        // languages.
104                        separator = "/";
105                }
106
107                ShallowEntity parent = entity.getParent();
108                while (parent != null) {
109                        if (parent.getType() == EShallowEntityType.TYPE || parent.getType() == EShallowEntityType.MODULE) {
110                                name = parent.getName() + separator + name;
111                        }
112
113                        parent = parent.getParent();
114                }
115
116                return name;
117        }
118
119        /** Determines the programming language of an entity. */
120        private static ELanguage determineLanguage(ShallowEntity entity) {
121                UnmodifiableList<IToken> tokens = entity.includedTokens();
122                CCSMAssert.isFalse(tokens.isEmpty(), "Shallow entities must have underlying tokens!");
123                return CollectionUtils.getAny(tokens).getLanguage();
124        }
125
126        /**
127         * Extracts the complete name of a C++ method or attribute. This includes
128         * modifiers (e.g. for destructors) and the complete type prefix for fully
129         * qualified method names (e.g. A::B::c).
130         */
131        private static String extractCppMethodOrAttributeName(ShallowEntity methodOrAttribute) {
132
133                CCSMAssert.isTrue(
134                                methodOrAttribute.getType() == EShallowEntityType.METHOD
135                                                || methodOrAttribute.getType() == EShallowEntityType.ATTRIBUTE,
136                                "May only be used for method or attribute!");
137
138                List<IToken> tokens = methodOrAttribute.includedTokens();
139                String name = methodOrAttribute.getName();
140
141                int index = 0;
142
143                // We advance the index to find the position of the name in the token
144                // stream. As for constructors the name of the method and the class are
145                // the same, the second part of the condition checks if the identifier
146                // is followed by the scope separator ("::"), which indicates that this
147                // is a class name.
148                while ((index < tokens.size() && !tokens.get(index).getText().equals(name))
149                                || (index + 1 < tokens.size() && tokens.get(index + 1).getType() == ETokenType.SCOPE)) {
150                        index += 1;
151                }
152
153                if (index >= tokens.size()) {
154                        return name;
155                }
156
157                // for destructors, the scanner produces a separate token for the "~"
158                // character, which thus must be handled explicitly here
159                if (index >= 1 && tokens.get(index - 1).getType() == ETokenType.COMP) {
160                        index -= 1;
161                        name = "~" + name;
162                }
163
164                // expand the name while the current position is prepended by a scope
165                // marker ("::")
166                while (index >= 2 && tokens.get(index - 1).getType() == ETokenType.SCOPE) {
167                        index -= 2;
168                        name = tokens.get(index).getText() + CPP_SCOPE + name;
169                }
170
171                return name;
172        }
173}